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

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

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

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

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

И снова добро пожаловать! Если вы еще не забыли, в прошлый раз мы фактически добавили возможность загружать файлы. В этот раз будем отталкиваться от этого. Посмотрите, я добавил здесь только лишь один импорт и несколько строк, т.к далее они нам пригодятся, а в остальном большая часть базового кода в этой итерации останется прежней.

Шаг пятый

package main

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

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)

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

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

Вот мы и подошли к нашей первой новой функции createSaveDirectory. В нем довольно много логики, поэтому я постараюсь разбить объяснение на части. Сама функция принимает строку, в данном случае поле заголовка, определенное в нашей структуре (которую мы создали из входящего JSON), и возвращает строку или ошибку. Для того, чтобы сохранить файл, первым делом необходимо получить абсолютный путь к каталогу, в котором в данный момент работает программа. На моей локальной машине, это /e/Projects/Go/src/downloader.

func createSaveDirectory(title string) (string, error) {  
  dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
  if err != nil {
    log.Fatal(err)
  }

В случае того, если что-то из этого не работает, мы не просто выбрасываем ошибку и возвращаемся, мы выкидываем фатальную ошибку и полностью завершаем работу. Т.к у нас уже есть каталог, хранящийся в переменной dir, мы используем filepath.Join (как показано ниже), чтобы добавить переданный заголовок в качестве имени каталога, в котором сохраним файл. Для примера, использующего наш предыдущий JSON, это будет /e/Projects/Go/src/downloader/Attempting Go. На данном этапе мы можем получить ошибку, ведь мы предполагаем, что наш ввод правильно сформирован и не вызовет проблем в файловой системе.

Никогда так не делайте в продакшене. Мы еще вернемся к этим вопросам. Все таки не хочется рисковать и писать в неправильное место на сервере. Как бы то ни было, далее, когда у нас есть путь к файлу, мы вызываем  os.Stat,  чтобы понять, существует ли этот путь на самом деле, если нет, пытаемся его создать. Если путь существует, то мы просто его возвращаем тому, кто его вызывал. 

path := filepath.Join(dir, title)
  _, err = os.Stat(path)

  // directory does not exist we should create it.
  if err != nil {
    err = os.Mkdir(path, 0755)
    if err != nil {
      log.Fatal(err)
    }
  }

  return path, nil
}

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

Я изменил вызов http.Get, потому что так как я и предполагал,  у него были проблемы с неправильно закодированными URL. Как же можно обойтись без проблем с адресами содержащими + или% 20 вместо пробелов?  Я попробовал несколько разных способов кодирования URL с помощью встроенных пакетов URL, но, к сожалению, так и не нашел того, что мне нужно. И все таки что-то нужно было делать, поэтому я решил использовать строки. Их можно заменить сразу на правильную кодировку% 20. В этот раз я сделал это прямо в вызове GET,  а в следующей части мы рассмотрим проблемы проверки входных данных также и в других функциях.

response, err := http.Get(strings.Replace(downloadRequest.Location, " ", "%20", -1))
  if err != nil {
    log.Println(err)
    return err
  }
  defer response.Body.Close()

Вот, наконец мы получаем тело ответа и сразу же проверяем, что нам есть куда его сохранить. Итак, вызываем функцию createSaveDirectory, описанную выше. Можно переместить эти несколько строк выше http.Get, но, в конце концов, я считаю, что если у нас возникла проблема с локальной подготовкой к сохранению, то логично бы было отменять загрузку.

save, err := createSaveDirectory(downloadRequest.Title)
  if err != nil {
    log.Println(err)
    return err
  }

  out, err := os.Create(filepath.Join(save, filepath.Base(u.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)
}

А вот мы и подошли к концу это части. В следующий раз мы попробуем очистить код и исправить те неточности, которые мы получаем в результате некорректных данных, так что еще соберу некоторую статистику проверок входных данных. Затем мы определим, как запустить этот код в «облаке», и, возможно, коснемся на более низком уровне коснемся того, как мы на самом деле его используем.

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

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

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

Попробовать