04.6
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 4.4 Дублирование отправки
|
||||
|
||||
Не знаю, встречалось ли Вам, что на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения.
|
||||
Не знаю, встречали ли Вы, как на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения.
|
||||
|
||||
Решением этой задачи является добавление в форму скрытого поля с уникальным токеном и проверка этого токена перед перед обработкой введенных данных. А если для отправки формы Вы используете Ajax, можно после того, как данные отправлены, сделать кнопку отправки неактивной.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="submit" value="Login">
|
||||
|
||||
Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже с этой формы данные.
|
||||
Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (см. Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже данные с этой формы:
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) // получаем метод запроса
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
Рисунок 4.4 Содержимое браузера после добавления токена
|
||||
|
||||
Если обновлять страницу, можно видеть каждый раз новый токен. Этот факт обеспечивает то, что каждая форма уникальна.
|
||||
Если обновлять страницу, можно видеть каждый раз новый токен. Это обеспечивает то, что каждая форма уникальна.
|
||||
|
||||
На данный момент Вы можете предотвращать множество атак на основе дублирования отправки посредством добавления в формы токенов, но все атаки такого типа предотвратить таким образом нельзя. Для этого нужно проделать еще больше работы.
|
||||
|
||||
|
||||
168
ru/04.5.md
Normal file
168
ru/04.5.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 4.5 Загрузка файлов
|
||||
|
||||
Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фортографии. Как можно реализовать эту функцию?
|
||||
|
||||
Для этого нужно добавить в форму, через которую будут закачиваться фотографии, свойство `enctype`. Оно имеет три значения:
|
||||
|
||||
```
|
||||
application/x-www-form-urlencoded Кодировать все символы перед закачкой (по умолчанию).
|
||||
multipart/form-data Не кодировать. Если в форме есть функционал закачки файлов, Вы должны использовать это значение.
|
||||
text/plain Конвертировать пробелы в "+", но не кодировать специальные символы.
|
||||
```
|
||||
|
||||
|
||||
Поэтому, содержимое HTML формы для загрузки файлов должно выглядеть так:
|
||||
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<title>Загрузка файлов</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
|
||||
<input type="file" name="uploadfile" />
|
||||
<input type="hidden" name="token" value="{{.}}"/>
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
Для работы с этой формой мы должны добавить функцию на сервере:
|
||||
|
||||
```
|
||||
http.HandleFunc("/upload", upload)
|
||||
|
||||
// обработка закачки
|
||||
func upload(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Метод:", r.Method)
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("upload.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, handler, err := r.FormFile("uploadfile")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Fprintf(w, "%v", handler.Header)
|
||||
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, file)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Как Вы можете видеть, для загрузки файлов нужно вызвать функцию `r.ParseMultipartForm`. Эта функция имеет аргумент `maxMemory`. После вызова `ParseMultipartForm` файл будет сохранен в памяти сервера с размером `maxMemory`. Если размер файла больше, чем `maxMemory`, остальная часть данных будет сохранена во временном файле в системе. Вы можете использовать `r.FormFile` для того, чтобы работать с файлом, и `io.Copy` для того, чтобы сохранить файл в файловой системе.
|
||||
|
||||
Для того, чтобы получить доступ к другим полям формы, не относящимся к загрузке файлов, Вам не нужно вызывать `r.ParseForm`, так как Go вызовет эту функцию, когда понадобится. Также, вызов `ParseMultipartForm` один раз достаточен - многократные вызовы ничего не меняют.
|
||||
|
||||
Для загрузки файлов мы используем следующие три шага:
|
||||
|
||||
1. Добавить в форму `enctype="multipart/form-data"`.
|
||||
2. Вызвать на стороне сервера `r.ParseMultipartForm`, чтобы сохранить файл в память или во временный файл.
|
||||
3. Вызвать `r.FormFile` для обработки файла и сохранения его в файловую систему.
|
||||
|
||||
Хэндлером для файла является `multipart.FileHeader`. У него следующая структура:
|
||||
|
||||
```
|
||||
type FileHeader struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
// соджержит отфильтрованные или неэкспортируемые поля
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Рисунок 4.5 Вывод информации на сервере после получения файла
|
||||
|
||||
## Загрузка файлов клиентом
|
||||
|
||||
Я показал Вам пример, как можно использовать форму для загрузки файлов. Мы можем сделать так, чтобы загружать файлы через форму без участия человека:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func postFile(filename string, targetUrl string) error {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
|
||||
// этот шаг очень важен
|
||||
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
|
||||
if err != nil {
|
||||
fmt.Println("ошибка записи в буфер")
|
||||
return err
|
||||
}
|
||||
|
||||
// процедура открытия файла
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("ошибка открытия файла")
|
||||
return err
|
||||
}
|
||||
|
||||
//iocopy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
|
||||
resp, err := http.Post(targetUrl, contentType, bodyBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resp_body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(resp.Status)
|
||||
fmt.Println(string(resp_body))
|
||||
return nil
|
||||
}
|
||||
|
||||
// пример использования
|
||||
func main() {
|
||||
target_url := "http://localhost:9090/upload"
|
||||
filename := "./astaxie.pdf"
|
||||
postFile(filename, target_url)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Этот пример показывает, как можно использовать клиента для загрузки файлов. Он использует `multipart.Write` для того, чтобы записывать файлы в кэш, и посылает их на сервер посредством метода POST.
|
||||
|
||||
Если у Вас есть другие поля, которые нужно писать в данные, такие, как имя пользователя, вызывайте `multipart.WriteField` по необходимости.
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [Содержание](preface.md)
|
||||
- Предыдущий раздел: [Дублирование отправки](04.4.md)
|
||||
- Следующий раздел: [Итоги раздела](04.6.md)
|
||||
11
ru/04.6.md
Normal file
11
ru/04.6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 4.6 Итоги раздела
|
||||
|
||||
В этом разделе мы изучили основные моменты того, как работать с данными в Go, посредством нескольких примеров, таких как вход пользователей и загрузка файлов. Мы также заострили внимание на том, что проверка данных крайне важна для безопасности сайта, а также посвятили одну секцию тому, как фильтровать входные данные посредством регулярных выражений.
|
||||
|
||||
Я надеюсь, что теперь Вы больше знаете о процессе коммуникации между клиентом и сервером.
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [Содержание](preface.md)
|
||||
- Предыдущий раздел: [Загрузка файлов](04.5.md)
|
||||
- Следужщий раздел: [Базы данных](05.0.md)
|
||||
Reference in New Issue
Block a user