请帮我弄清楚这个Web代理代码有什么问题

时间:2009-08-11 18:02:14

标签: java sockets groovy

我想为练习编写一个Web代理,这是我到目前为止的代码:


// returns a map that contains the port and the host
def parseHostAndPort(String data) {
    def objMap // this has host and port as keys
    data.eachLine { line ->
        if(line =~ /^(?i)get|put|post|head|trace|delete/) {
            println line
            def components = line.split(" ")
            def resource = components[1]
            def colon = resource.indexOf(":")
            if(colon != -1) {
                URL u = new URL(resource)
                def pHost = u.host
                def pPort = u.port
                return (objMap = [host:pHost,port:pPort])
            }
            else {
                return (objMap = [host:resource,port:80])
            }
        }
    }
    return objMap
}

// reads a http request from a client
def readClientData(Socket clientSocket) {
    def actualBuffer = new StringBuilder()
    InputStream inStream = clientSocket.inputStream
    while(true) {
        def available = inStream.available()
        if(available == 0)
        break;
        println "available data $available"
        def buffer = new byte[available]
        def bytesRead = inStream.read(buffer,0,available)
        actualBuffer << new String(buffer)
    }
    return actualBuffer.toString()
}

def sock = new ServerSocket(9000)
sock.reuseAddress = true
while(true) {
    sock.accept { cli ->
        println "got a client"
        def data = readClientData(cli)
        def parsed = parseHostAndPort(data)
        def host = parsed["host"]
        def port = parsed["port"]

        println "got from client $data"

        def nsock = new Socket(host,port)
        nsock << data // send data received from client to the socket
        nsock.outputStream.flush() 
        def datax = readClientData(nsock)
        println "got back $datax"
        cli << datax // send the client the response
        cli.outputStream.flush()
        cli.close()
    }
}

现在,它所做的只是:

  • 阅读我的浏览器发送的HTTP请求

  • 解析主机和端口

  • 连接到该主机,并写入从客户端收到的数据

  • 向客户端发回从主机收到的数据

但......它不会一直有效。有时它会提出一个好的要求,有时候不是。我认为这是一个缓冲问题,我不确定。问题是,我添加了flush次呼叫,但仍然没有。

你能发现我做错了吗?

编辑:

  • 我注意到,如果我添加一些sleep次调用,代理似乎会“处理”更多的请求,但不是所有请求。
  • 收集赏金,帮我找出我做错了什么。用于Web代理的常规“算法”是什么?我在哪里偏离它?谢谢!

6 个答案:

答案 0 :(得分:4)

乔纳森走在正确的轨道上。问题部分在于您使用available()。方法available没有说“它完成了吗?”它说“目前有没有可用的数据?”。因此,在您提出请求后,将立即没有可用的数据,并且取决于处理过程中可能发生的网络时间,但这并不意味着不再有任何数据,因此您的break是过早。

此外,InputStream.read(byte[] ...)系列方法始终允许返回的字节数少于您要求的字节数。数组长度或偏移量,长度对约束最大值,但总是可以减少。所以,你的这段代码:

    def buffer = new byte[available]
    def bytesRead = inStream.read(buffer,0,available)
    actualBuffer << new String(buffer)

可以创建一个大数组,但是只能在读取中获得半数数据,但仍然将完整的缓冲区(带有其未跟踪的数组元素)附加到String上。

这是一个依赖于InputStream.read(...)将永远不会返回的事实的修订版,除非它已经结束或者有一些数据可用(但不一定与你的要求一样多)。

// reads a http request from a client
def readClientData(Socket clientSocket) {
    def actualBuffer = new StringBuilder()
    InputStream inStream = clientSocket.inputStream
    int bytesRead = 0;
    byte[] buffer = new byte[16 * 1024];
    while((bytesRead = inStream.read(buffer)) >= 0) { // -1 on EOF
        def bytesRead = inStream.read(buffer,0,bytesRead); // only want newly read bytes
        actualBuffer << new String(buffer)
    }
    return actualBuffer.toString()
}

那就是说,你还有其他一些问题:

  • 你将整个响应拉入内存,当你应该将它在字节泵循环中直接复制到客户端的响应输出流中时(如果它是一个多千兆字节的响应会发生什么)
  • 你正在使用字符串来存储二进制数据 - 假设所有字节在默认的CharacterEncoding中工作正常,这在UTF-8或US-ASCII中可能是正确的,但是不适用于其他语言环境

答案 1 :(得分:3)

答案 2 :(得分:1)

Ry4an提出了一些好处。如果你想看看如何构造一个小但完美形成的代理,看看用Python编写的Tiny HTTP Proxy - 你可以看到所有需要解决的问题,并且移植代码相当简单到Groovy。我已经将代理用于测试目的并且运行良好。

答案 3 :(得分:0)

我建议你熟悉HTTP protocol specification。 HTTP比单独的TCP连接上的单个请求 - 响应更复杂 - 即如果客户端或服务器尝试使用持久连接,则实现将失败。

答案 4 :(得分:0)

readClientData(套接字)中是否存在竞争条件?您似乎正在立即检查数据是否可用,但有可能尚未收到数据;你只需退出循环而不是等待接收第一个数据。

答案 5 :(得分:0)

客户端套接字是否阻塞?如果是这样,您可能想尝试非阻塞I / O或设置套接字超时。