DevGang
Авторизоваться

Изучаем Go - создание загрузчика (часть 2)

В прошлый раз мы опеределились с планом создания простого сервера на Go, который бы загружал удаленные файлы. В этот раз я бы уже хотел приступить к написанию настоящего кода. Я постараюсь документировать все, что помогло мне собрать программу вместе со всеми ссылками, которые я использовал. Я также пытался ясно и понятно именовать переменные. Например, используя ответ http.ResponseWriter , запросите *http.Request вместо w http.ResponseWriter, r *http.Request. Надеюсь на то, что любой человек с любым опытом сможет разобраться в коде.

Шаг первый

Наша первая задача - наконец то увидеть код на экране. Мы собираемся создать несколько пакетов приложения, один формата main и другой формата fmt , чтобы можно было выводить результат в стандартный вывод.

package main

import "fmt"

Мы будем использовать объект JSON, с которым определились ранее, и создадим его структуру на сайте JSON-to-Go. Он позволяет удобно и просто работать с JSON, поэтому, если собираетесь работать с JSON в Go,  рекомендую добавить его в закладки.

type download struct {  
  Title string `json:"title"`
  Location string `json:"location"`
}

Для начала заставим программу при запуске выводить слово «Downloader» и сделаем это все в функции main . После этого создадим тестовую структуру, которую потом тоже выведем.

func main() {  
  fmt.Printf("Downloader")

  download := download{
    Title: "title-test",
    Location: "location-test",
  }

  fmt.Printf("%v", download)
}

И при запуске мы увидим следующее: 

$ go run downloader.go
Downloader  
{title-test location-test}

Шаг второй

Первое, на что обратим внимание, это то, что теперь мы используем два новых импорта. Эти импорты: io/ioutil и net/http являются частью стандартной библиотеки Go и возьмут на себя некоторую функциональность. Стандартная библиотека предоставляет разработчику множество инструментов, необходимых для создания различных программ. Надо лишь научится использовать их друг с другом. А далее вы узнаете, что даже если стандартного библиотечного пакета для функции не существует, скорей всего кто то в сообществе уже подумал и исправил это. 

package main

import (  
  "fmt"
  "io/ioutil"
  "net/http"
)

type download struct {  
  Title string `json:"title"`
  Location string `json:"location"`
}

Ну вот мы и подошли к первой новой функции - status (статусу). В настоящее время запрос «/» на сервер выдаст код ответа 200 с телом «Hello!». Вскоре мы поменяем ответ и будем возвращать информацию о сервере для проверки его работоспособности.

func status(response http.ResponseWriter, request *http.Request) {  
  fmt.Fprintf(response, "Hello!")
}

Новая функция handleDownloadRequest будет основной задачей серверной части нашей программы. Пока что она примет запрос «/ download», выведет тело запроса в консоль и слово «Download!». в браузер.

func handleDownloadRequeset(response http.ResponseWriter, request *http.Request) {  
  r, err := ioutil.ReadAll(request.Body)
  if err != nil {
    fmt.Println(err)
  }
  defer request.Body.Close()
  fmt.Println(string(r))

  fmt.Fprintf(response, "Download!")
}

Обновим функцию  main, чтобы использовать новые функции для настройки обработчиков HTTP( HTTP handlers ). Наконец, мы запускаем сервер, прослушивающий порт 3000.

func main() {  
  fmt.Println("Downloader")

  http.HandleFunc("/", status)
  http.HandleFunc("/download", handleDownloadRequest)
  http.ListenAndServe(":3000", nil)
}

Шаг третий

На этот раз большинство изменений происходят в функции handleDownloadRequest. Однако, если вы посмотрите внимательно, вы увидите, что я также добавил новые импорты encoding/json и log. Так как у нас серверное приложение, я хотел использовать встроенные функции логера и выводить их в консоль. Я пока не уверен, как поступить лучше, отправлять логи в файл или просто выводить в консоль.

package main

import (  
  "encoding/json"
  "fmt"
  "io/ioutil"
  "log"
  "net/http"
)

type download struct {  
  Title string `json:"title"`
  Location string `json:"location"`
}

func status(response http.ResponseWriter, request *http.Request) {  
  fmt.Fprintf(response, "Hello!")
}

А вот и сердце нашей программы. К тому же, здесь видны некоторые существенные изменения. Сначала, мы создаем структуру с именем downloadReqest для хранения нашего входящего заголовка и URL. Я так же улучшил обработку ошибок, добавил при необходимости возможность вывода реальных ошибок в браузере.

func handleDownloadRequest(response http.ResponseWriter, request *http.Request) {  
  var downloadRequest download
  r, err := ioutil.ReadAll(request.Body)
  if err != nil {
    http.Error(response, "bad request", 400)
    log.Println(err)
    return
  }
  defer request.Body.Close()

Предполагая, что начальный запрос прошел мы можем прочитать тело и далее вставляем запрос JSON в нашу структуру. Вызов json.Unmarshal сделает всю грязную работу за нас. Если он не справится, мы можем вернуть ошибку в браузер. Вот как мы выводим сообщение об ошибке в json.Unmarshal 

err = json.Unmarshal(r, &downloadRequest)
  if err != nil {
    http.Error(response, "bad request: "+err.Error(), 400)
    log.Println(err)
    return
  }
  log.Printf("%#v", downloadRequest)

  fmt.Fprintf(response, "Download!")
}

func main() {  
  log.Println("Downloader")

  http.HandleFunc("/", status)
  http.HandleFunc("/download", handleDownloadRequest)
  http.ListenAndServe(":3000", nil)
}

Мы с вами довольно много успели сделать! Быстро прошли путь от базового скелета до рабочего сервера. Теперь займемся изменением функции handleDownloadRequest, добавив к ней еще одну  функцию getFile, которая нам понадобится для получения фактического файла.

Другие статьи из цикла:

#Golang
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться