Как перемотать io.ReadCloser в Голанге? - PullRequest
0 голосов
/ 14 января 2019

Я постоянно пойман, прочитав io.ReadCloser и затем забыв, что я читал это раньше, и когда я читаю это снова, я получаю пустую полезную нагрузку. Хотелось бы, чтобы был какой-нибудь чек на мою глупость. Тем не менее, я думаю, что могу использовать TeeReader, но он не отвечает моим ожиданиям:

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        buf := &bytes.Buffer{}
        tee := io.TeeReader(r.Body, buf)
        body, err := ioutil.ReadAll(tee)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        log.Println("body", string(body))
        payload, err := httputil.DumpRequest(r, true)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        log.Println("dump request", string(payload))
        w.WriteHeader(http.StatusOK)
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Тело отсутствует в моей строке журнала "dump dump".

т.е. когда я бегу curl -i -X POST --data '{"username":"xyz","password":"xyz"}' http://localhost:8080

Я хочу полный запрос оригинала:

2019/01/14 11:09:50 dump request POST / HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Length: 35
Content-Type: application/x-www-form-urlencoded
User-Agent: curl/7.63.0

{"username":"xyz","password":"xyz"}

Чего мне не хватает?

Ответы [ 4 ]

0 голосов
/ 16 января 2019

https://golang.org/pkg/net/http/httputil/#DumpRequest

DumpRequest возвращает данный запрос в своем представлении HTTP / 1.x. Он должен использоваться только серверами для отладки клиентских запросов.

Очевидно, что DumpRequest предназначен для использования в Dubug.

Но если тебя это не волнует. Годок также упомянул:

Если body имеет значение true, DumpRequest также возвращает тело. Для этого он использует req.Body, а затем заменяет его новым io.ReadCloser, который возвращает те же байты.

Таким образом, вы можете сначала вызвать DumpRequest, а затем ReadAll from Body, потому что Body остается тем же после DumpRequest.

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        payload, err := httputil.DumpRequest(r, true)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        log.Println("dump request", string(payload))        

        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        log.Println("body", string(body))
        w.WriteHeader(http.StatusOK)
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}
0 голосов
/ 14 января 2019

Вы не можете перематывать io.ReadCloser, если базовое значение также не является io.ReadSeeker.

У io.ReadCloser, по определению, есть ровно два метода: Read и Close. Так что тут явно нет возможности перемотать.

У io.ReadSeeker, напротив, есть два метода: Read и Seek, последний, который позволяет перематывать.

Если вам нужно принять только io.ReadCloser s, которые также доступны для поиска, вы можете легко объединить эти два:

type ReadSeekCloser interface {
    io.Reader
    io.Seeker
    io.Closer
}

Теперь вы можете использовать пользовательский тип ReadSeekCloser вместо io.ReadCloser, и у вас будет возможность перемотать свой читатель.

Конечно, немногие io.ReadCloser в дикой природе на самом деле соответствуют этому интерфейсу (os.File будет основным, который это делает). Если у вас есть io.ReadCloser, который не реализует метод Seek (например, сетевой поток), возможно, самый простой способ сделать его Seek способным - это сбросить содержимое в файл, а затем открыть этот файл. Существуют и другие способы сделать поиск в буфере в памяти доступным (например, bytes.NewReader), но для вариантов потребуется сначала прочитать поток в память или на диск.

0 голосов
/ 14 января 2019

Тела запроса не доступны для поиска. Перемотки нет, они не сохраняются в памяти. Когда эти данные читаются, они считываются из сетевого потока. Представьте большую загрузку, буферизация всех этих данных в памяти по умолчанию была бы бесполезной.

Тем не менее, есть некоторые вещи, которые вы можете сделать, чтобы получить желаемый результат. Это включает в себя замену r.Body после того, как вы прочитали его.

Вы упомянули о желании получить чек. Я обнаружил, что объявление буфера и использование только этого буфера может помочь. Если хотите, вы можете даже заменить r.Body на этот буфер. Вам все еще нужно помнить, чтобы «перемотать» назад с помощью Seek.

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        var body ReedSeekCloser // Declare body as this interface, now it is a little easier to remember to use body, and it is seekable.
        body = &bytes.Buffer{}
        _, err := io.Copy(body, r.Body)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        log.Println("body", string(body))
        r.Body = body
        body.Seek(0, 0)
        payload, err := httputil.DumpRequest(r, true)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        log.Println("dump request", string(payload))
        w.WriteHeader(http.StatusOK)
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}
0 голосов
/ 14 января 2019

Как перемотать io.ReadCloser в Go [...]?

Вы не можете. ReadCloser может быть прочитан и закрыт. Если реальный базовый тип не имеет какого-то метода, перемотать его вы просто не сможете. (В вашем случае вы можете просто использовать bytes.Buffer, возможно, после добавления метода Close через io / ioutil.ReadCloser в качестве Request.Body; но это не «перематывание», а «замена».)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...