У вас включен AdBlock или иной блокировщик рекламы.

Пожалуйста, отключите его, доход от рекламы помогает развитию сайта и появлению новых статей.

Спасибо за понимание.

В другой раз
DevGang блог о програмировании
Авторизоваться

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

В прошлый раз мы остановились на том, что закончили собирать нашу конечную точку загрузки. В этот раз мы расширим наш проект, чтобы уже наконец-то попробовать загрузить файл. Все это означает, что мы также научимся тестировать конечную точку загрузки. Вы готовы? Давайте скорей погружаться!

Шаг четвертый

На первый взгляд становится страшно, но не пугайтесь! Мы всего лишь добавили еще несколько импортов. Мы впервые используем такие импорты, как io, net/url, os и path/filepath. Не забудьте использовать их, они совсем не страшны. Теперь давайте переместимся с вами до самого конца, к функции handleDownRequest.

package main

import (  
  "encoding/json"
  "fmt"
  "io"
  "io/ioutil"
  "log"
  "net/http"
  "net/url"
  "os"
  "path/filepath"
)

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

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

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()

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

Выглядит все довольно знакомо, как в нашей прошлой версии. Мы еще ничего не изменяли, только лишь добавили новые импорты. Здесь наша структура заполняется заголовком и URL для загрузки. Давайте передадим их в новую функцию getFile. getFile вернет ошибку или nil, если операция пройдет успешно. Иначе мы просто вернем браузеру ошибку 500 «internal server». Более специфичные вещи мы будем регистрировать во внутренних логах. В целях безопасности, мы хотим избежать утечки информации о сервере обратно в браузер всегда, когда это возможно. Хотя наш проект всего лишь учебный, но хорошо бы об этом помнить даже и сейчас. 

err = getFile(downloadRequest)
  if err != nil {
    http.Error(response, "internal server error", 500)
    return
  }

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

Пока что getFile еще не очень большая функция. Как отмечалось выше, мы передаем нашу структуру загрузки и возвращаем ошибку (или ноль). Для того, чтобы сохранить файл на диск, я использую url.Parse из пакета net/url для того, чтобы распарсить URL на составные части.

func getFile(downloadRequest download) error {  
  parsedUrl, err := url.Parse(downloadRequest.Location)
  if err != nil {
    log.Println(err)
    return err
  }

Наконец, сердце нашего проекта! http.Get берет URL и запрашивает удаленный ресурс. Однако  мы предполагаем, что любой URL, полученный из JSON, в порядке, следовательно здесь могут быть проблемы. Url, по крайней мере, будет либо «well-formed», либо он вообще не должен был пройти через функцию url.Parse. Далее мы, скорей всего, организуем более глубокую проверку, для того чтобы сразу попытаться определять, следует ли нам даже пытаться выполнить запрос GET.

response, err := http.Get(downloadRequest.Location)
  if err != nil {
    log.Println(err)
    return err
  }
  defer response.Body.Close()

Давайте предположим, что все прошло как надо, и в итоге мы получили удаленный ресурс. Мы собираемся использовать parsedUrl.Path и filepath.Base , чтобы извлечь то, что должно быть правильным именем файла. А что если мы получим URL, в котором нет правильного имени файла? Именно поэтому на этапе проектирования мы закладываем возможность изменения. Мы контролируем среду тестирования и у нас все хорошо!

out, err := os.Create(filepath.Base(parsedUrl.Path))
  defer out.Close()
  _, err = io.Copy(out, response.Body)
  if err != nil {
    log.Println(err)
    return err
  }
  return nil
}

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

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

Тестирование

Чтобы облегчить наше тестирование, мы будем использовать фантастическую платформу Postman. Если собираетесь что либо делать с API, я рекомендую добавить ее в свой инструментарий как обязательный пункт. Я не собираюсь вдаваться в подробности использования самого Postman, это будет заданием для вас, дорогие читатели. 

Мы будем использовать данный объект JSON для этого раунда тестирования

{
  "title": "Attempting Go",
  "location": "https://shindakun.glitch.me/content/images/2018/01/attemptinggo-2.png"
}

После запуска текущей версии кода мы увидим что-то вроде:

$ go run downloader.go
2018/01/11 11:06:24 Downloader

Теперь, если мы отправим наш запрос PUT в Postman, мы увидим:

2018/01/11 11:07:32 main.download{Title:"Attempting Go", Location:"https://shindakun.glitch.me/content/images/2018/01/attemptinggo-2.png"}

Вместе с недавно созданным PNG в том же каталоге: 

-rw-r--r-- 1 steve 197609 74860 Jan 11 11:07 attemptinggo-2.png

Потрясающие! В следующий раз мы немного очистим код и добавим возможность сохранять файлы в подкаталог. А после, мы уже перейдем к первоначальному развертыванию.

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

Присоеденяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать