Изучаем 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)
}
А вот мы и подошли к концу это части. В следующий раз мы попробуем очистить код и исправить те неточности, которые мы получаем в результате некорректных данных, так что еще соберу некоторую статистику проверок входных данных. Затем мы определим, как запустить этот код в «облаке», и, возможно, коснемся на более низком уровне коснемся того, как мы на самом деле его используем.
- Изучаем Go - создание загрузчика (часть 1)
- Изучаем Go - создание загрузчика (часть 2)
- Изучаем Go - создание загрузчика (часть 3)
- Изучаем Go - создание загрузчика (часть 4)
- Изучаем Go - создание загрузчика (часть 5)
- Изучаем Go - Использование REST API
- Изучаем Go - Продолжаем работать с REST API
- Изучаем Go - Отправка REST-запросов
- Изучаем Go - Используем REST API в паре с шаблонами проектирования
- Изучаем Go - Повторная отправка электронной почты через API
Изучаем Go - Давайте станем модульными!скороИзучаем Go - Давайте станем модульными снова!скороИзучаем Go - Сборка DevLog Часть 01скороИзучаем Go - Сборка DevLog Часть 02скоро