在不进行大量分配的情况下,在io.ReadCloser中查找字符串

时间:2018-07-11 16:34:14

标签: http go memory

我有一个很大的io.ReadCloser是从HTTP处理程序函数中的http.Request获得的。我需要将请求代理到另一台服务器,但是首先,我想在正文中找到与Title: (\w+)之类的正则表达式匹配的字符串。这很困难-将整个主体复制到新的缓冲区中以进行操作会占用过多的内存,而我尝试使用regexp.FindReaderSubmatchIndex,但它只给我结果的索引,而不是实际的字符串。

执行此操作的最佳方法是什么?令牌生成器和JSON解码器等似乎可以在io流上运行,这是一个非常简单的用例。有人可以指出正确的方向吗?

2 个答案:

答案 0 :(得分:1)

我将为此使用io.TeeReader并将特殊writer传递给TeeReader构造函数。考虑将以下代码作为伪代码,因为有些边缘情况我们在这里不处理:

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "regexp"
)

type Finder struct {
    Regexp *regexp.Regexp
    match  string
}

//Write implements io.Writer interface
func (f *Finder) Write(p []byte) (int, error) {
    if f.match == "" {
        f.match = string(f.Regexp.Find(p))
    }

    return len(p), nil
}

func Handler(w http.ResponseWriter, r *http.Request) {
    f := &Finder{
        Regexp: regexp.MustCompile("Title: ([a-zA-Z0-9]+)"),
    }

    r.Body = ioutil.NopCloser(io.TeeReader(r.Body, f))

    //pass request to another server

    fmt.Println(f.match)
}

答案 1 :(得分:0)

这是我的解决方案。我在响应主体和它的阅读器之间放置了一个管道,并用io.TeeReader包裹了阅读器,以便在我从它读取时将其写入管道。我将其包裹在bufio.Scanner中并扫描了几行。完成扫描线后,我确定要消耗主体的其余部分(使用io.Copy(ioutil.Discard, body)),以便将主体的其余部分写入管道。

if request.Body == nil {
    proxy(request)
    return
}

// The body is *not* nil,
// so we're going to process it line-by-line.

bodySrc := request.Body             // Original io source of the request body.
pr, pw := io.Pipe()                 // Pipe between bodySrc and request.Body.
body := io.TeeReader(bodySrc, pw)   // When you read from body, it will read from bodySrc and writes to the pipe.
request.Body = ioutil.NopCloser(pr) // The other end of the pipe is request.Body. That's what proxy() will read.

go func() {

    scanner = bufio.NewScanner(body)
    for scanner.Scan() {
        x := scanner.Bytes()
        if processLine(x) {
            break
        }
    }

    // We're done with the body,
    // so consume the rest of it and close the source and the pipe.
    io.Copy(ioutil.Discard, body)
    bodySrc.Close()
    pw.Close()

}()

// As proxy reads request.Body, it's actually keeping up
// with the scanning done in the above goroutine.
proxy(request)