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

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

Эта часть будет немного отличаться от предыдущих. Я думаю мы немного обновим Go код и, тем самым, избавимся от возможных проблем с очисткой, которые я упоминал в прошлый раз. Как только закончим с этим, займемся версткой кода для создания расширения Chrome! И на этой ноте давайте начнем!

Шаг шестой

И начнем мы с добавления библиотеки Sanitize от kennygrant. Чтобы добавить пакет в Go, используем команду  go get  и пропишем ее в командной строке. 

go get github.com/kennygrant/sanitize

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

Небольшое уточнение, как говорит @rafaacioly, я могу использовать встроенные вызовы http.StatusBadRequest и http.StatusInternalServerError при ошибках HTTP. 

Это требует больше кода, но я считаю, что более коректным будет использование встроенных значений.

package main

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

  "github.com/kennygrant/sanitize"
)

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", http.StatusBadRequest)
    log.Println(err)
    return
  }
  defer request.Body.Close()

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

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

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

func createSaveDirectory(title string) (string, error) {  
  dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
  if err != nil {
    log.Fatal(err)
  }
  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
}

Ниже вы увидете, что мы указываем sanitize в двух местах. Первый раз при передаче заголовка в функцию createSaveDirectory- sanitize.BaseName. Второй при попытке сохранить файл с помощью os.Create - sanitize.Path. Благодаря этому мы позаботились об удалении любых символов, которые нам не нужны. Например, если кто-то пытается выполнить поиск по каталогу с заголовком, отправив JSON "title": "Fake Title ../../../../../etc/" Итак, по-прежнему директория сохранения будет создаваться как подпапка, в которой находится наше приложение и будет иметь имя Fake-Title-etc-. Имя файла очищается немного по-другому, но в итоге результат схож. Я не проводил исчерпывающий тест, но сейчас у нас все должно быть в порядке.

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

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

  // Encoding URL via path never seems to work as expected, fall back to
  // simply replacing spaces with %20's, for now.
  response, err := http.Get(strings.Replace(downloadRequest.Location, " ", "%20", -1))
  if err != nil {
    log.Println(err)
    return err
  }
  defer response.Body.Close()

  out, err := os.Create(filepath.Join(save, sanitize.Path(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 в этом посте. Теперь мы отправляемся на ускоренный курс по написанию расширений для Chrome! Я не буду вдаваться в подробности, пока остановлюсь только на трех основных файлах, которые входят в расширение. Чтобы углубиться в механику создания расширений, загляните в раздел extension section на портале разработчиков Chrome.

Расширения Chrome

Расширение обычно состоит из трех или четырех файлов. Наиболее важным является Манифест.json, т.к он предоставляет браузеру всю информацию, необходимую для загрузки расширения. На данный момент права доступа и фоновая запись, вероятно, являются наиболее важными разделами в файле. Тестирование мы проводим на localhost. 

{
  "manifest_version": 2,
  "name": "Send to Downloader",
  "short_name": "Send2DL",
  "description": "Sends JSON to download server.",
  "version": "0.0.1",
  "minimum_chrome_version": "38",
  "permissions": [
    "webRequest",
    "*://localhost/",
    "contextMenus",
    "activeTab"],
  "icons": {
    "16": "assets/download.png"
  },
  "background": {"page": "background.html"}
}

Наверное вы удивились, когда увидели код background.html внизу. Правильно, он ничего не делает, кроме загрузки main.js, что на самом деле не так уж просто.

<!DOCTYPE html>  
<html>  
  <body>
    <script src="main.js"></script>
  </body>
</html>

main.js- это сердце нашего расширения, так как эта часть, на самом деле, отвечает за сбор всех запросов. С помощью main.js, также можно добавить параметр загрузки в контекстное меню Chrome, вызываемое правой кнопкой мыши. При внимательном рассмотрении, вы увидите, что опция «Отправить загрузчику» появится, только если щелкнуть правой кнопкой мыши на изображение. На данный момент код просто берет заголовок текущей вкладки и использует его в качестве каталога, в котором мы хотим сохранить изображение. В будущем, на реальных проектах добавьте возможность сохранения ссылки и файла.

sendToDownloader = function(obj, tab) {  
  let downloaderUrl = 'http://localhost:3000/download';

  let title = tab.title;
  let imgUrl = obj.srcUrl;

  let payload = {
    title: "title,"
    location: imgUrl
  };

  let xhr = new XMLHttpRequest();

  xhr.open("POST", downloaderUrl, false);
  xhr.setRequestHeader('Content-type','application/json; charset=utf-8');
  xhr.send(JSON.stringify(payload));

  let result = xhr.responseText;
};

chrome.contextMenus.create({  
  title: "\"Send to downloader\","
  contexts:["image"],
  onclick: sendToDownloader
});

Вот мы и подошли к концу. Последний пост будет посвящен процессу финального развертывания приложения на движке Google Compute. Наконец-то мы увидим проект в действии. Далее, я могу продолжить работу с загрузчиком, но наверное уже не буду так документировать.

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

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

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

Попробовать

В этом месте могла бы быть ваша реклама

Разместить рекламу