Изучаем 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скоро