Java代理 - 无法正确地从HTTP GET / POST请求交换数据

时间:2015-12-08 14:58:41

标签: java sockets http-post

this post开始我尝试实现一个处理GETPOST请求的小代理服务器(只需将Handler类替换为下面的一个):

public static class Handler extends Thread {

    public static final Pattern CONNECT_PATTERN
            = Pattern.compile("CONNECT (.+):(.+) HTTP/(1\\.[01])", Pattern.CASE_INSENSITIVE);
    public static final Pattern GET_POST_PATTERN
            = Pattern.compile("(GET|POST) (?:http)://([^/:]*)(?::([^/]*))?(/.*) HTTP/(1\\.[01])", Pattern.CASE_INSENSITIVE);

    private final Socket clientSocket;
    private boolean previousWasR = false;

    public Handler(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try {
            String request = readLine(clientSocket, Integer.MAX_VALUE);

            Matcher connectMatcher = CONNECT_PATTERN.matcher(request);
            Matcher getNpostMatcher = GET_POST_PATTERN.matcher(request);

            System.out.println("Request: " +request);
            if (connectMatcher.matches()) {
                // ...

            } else if (getNpostMatcher.matches()) {
                String method = getNpostMatcher.group(1);
                String hostString = getNpostMatcher.group(2);
                String portString = getNpostMatcher.group(3);
                String lengthString = null;
                String line;
                ArrayList<String> buffer = new ArrayList<String>();
                Integer port = portString == null || "".equals(portString) ? 80 : Integer.parseInt(portString);
                Integer length = null;

                buffer.add(request);
                while ((line = readLine(clientSocket, Integer.MAX_VALUE)) != null) {
                    buffer.add(line);

                    if ("".equals(line)) break;

                    if (lengthString == null && line.startsWith("Content-Length: ")) {
                        lengthString = line.substring(16);
                        length = Integer.parseInt(lengthString);
                    }
                }

                try {
                    final Socket forwardSocket;
                    try {
                        forwardSocket = new Socket(hostString, port);
                        System.out.println("  " + forwardSocket);

                    } catch (IOException | NumberFormatException e) {
                        OutputStreamWriter outputStreamWriter
                                = new OutputStreamWriter(clientSocket.getOutputStream(), "ISO-8859-1");

                        e.printStackTrace();
                        outputStreamWriter.write("HTTP/" + connectMatcher.group(3) + " 502 Bad Gateway\r\n");
                        outputStreamWriter.write("Proxy-agent: Simple/0.1\r\n");
                        outputStreamWriter.write("\r\n");
                        outputStreamWriter.flush();
                        return;
                    }

                    PrintWriter printWriter = new PrintWriter(forwardSocket.getOutputStream());

                    for (String bufferedLine : buffer) {
                        printWriter.println(bufferedLine);
                    }
                    printWriter.flush();

                    if ("POST".equals(method) && length > 0) {
                        System.out.println ("Posting data ...");
                        if (previousWasR) { // skip \n if existing
                            int read = clientSocket.getInputStream().read();
                            if (read != '\n') {
                                forwardSocket.getOutputStream().write(read);
                            }
                            forwardData(threadId, clientSocket, forwardSocket, length, true); // only forward "Content-length" bytes
                        } else {
                            forwardData(threadId, clientSocket, forwardSocket, length, true); // only forward "Content-length" bytes
                        }
                    } 

                    System.out.println ("Forwarding response ...");
                    forwardData(threadId, forwardSocket, clientSocket, null, false);

                    if (forwardSocket != null) {
                        forwardSocket.close();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void forwardData(int threadId, Socket inputSocket, Socket outputSocket, Integer length, boolean isPost) {
        try {
            InputStream inputStream = inputSocket.getInputStream();
            try {
                OutputStream outputStream = outputSocket.getOutputStream();
                try {
                    byte[] buffer = new byte[4096];
                    int read;
                    if (length == null || length > 0) {
                        do {
                            if ((read = inputStream.read(buffer)) > 0) {
                                outputStream.write(buffer, 0, read);
                                if (inputStream.available() < 1) {
                                    outputStream.flush();
                                }
                                if (length != null) {
                                    length = length - read;
                                }
                            }
                        } while (read >= 0 && (length == null || length > 0));
                    }
                } finally {
                    if (!outputSocket.isOutputShutdown()) {
                        if (!isPost) {
                            outputSocket.shutdownOutput();
                        }
                    }
                }
            } finally {
                if (!inputSocket.isInputShutdown()) {
                    inputSocket.shutdownInput();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String readLine(Socket socket, Integer noOfBytes) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int next;
        readerLoop:
        while (noOfBytes-- > 0 && (next = socket.getInputStream().read()) != -1) {
            if (previousWasR && next == '\n') {
                previousWasR = false;
                continue;
            }
            previousWasR = false;
            switch (next) {
                case '\r':
                    previousWasR = true;
                    break readerLoop;
                case '\n':
                    break readerLoop;
                default:
                    byteArrayOutputStream.write(next);
                    break;
            }
        }
        return byteArrayOutputStream.toString("ISO-8859-1");
    }
}

在遇到POST请求的一些问题后,我发现以正确的方式关闭流非常重要。所以最后使用Internet Explorer时上面的代码工作得很好。

然而,使用其他浏览器,看起来流/套接字没有正确关闭,因为有时加载指示器已经运行了很长一段时间,尽管内容似乎已经加载了。有时,网站没有完全加载,线程似乎挂在......

if ((read = inputStream.read(buffer)) > 0) {
forwardData(...)中的

。我不知道我怎么知道,流是否可以提供一些数据 - 或者如何完全避免read的阻塞调用。

有人知道我做错了什么以及如何正确转发数据,以便所有浏览器正确加载内容而不会造成不必要的延迟?

1 个答案:

答案 0 :(得分:1)

看,问题是您使用forwardData()将服务器响应复制到客户端,因为它不涉及Content-Length:字段。

HTTP/1.1{HTTP/1.1 200 OK
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Content-Type: text/html; Charset=utf-8
Content-Encoding: gzip
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: *
X-RADID: P301511016-T104210110-C24000000000248666
P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"
Date: Tue, 15 Dec 2015 09:33:41 GMT
Content-Length: 1656

<1656 Bytes>

在上面的示例响应中,代理必须在从服务器读取1656字节后关闭流。如果没有,则读取将阻止。

所以你需要的是一些搜索Content-Length:字段响应的代码,例如:

private static void forwardData(int threadId, Socket inputSocket, Socket outputSocket, Integer length, boolean isPost) {
    int cLength = -1;
    int count = 0;
    try {
        InputStream inputStream = inputSocket.getInputStream();
        try {
            OutputStream outputStream = outputSocket.getOutputStream();
            try {
                byte[] buffer = new byte[4096];
                int read;
                if (length == null || length > 0) {
                    do {
                        if ((read = inputStream.read(buffer)) > 0) { // search for "Content-Length: "
                            if (cLength == -1) {
                                String response = new String(buffer, "UTF-8");
                                int pos = response.indexOf("Content-Length:");
                                if (pos > 0) {
                                    String lString = response.substring(pos + 16, pos + 24).replaceAll("([0-9]*).*\\n?\\r?.*", "$1");
                                    cLength = Integer.parseInt(lString);
                                }
                            }

                            if (cLength != -1) { // if length is given, count bytes from empty line on
                                if (count > 0) { // already started  - so just add
                                    count = count + read;
                                } else { // check if empty line exists,  "\r\n\r\n" or "\r\r"
                                    for (int n = 0; n < read; n++) {
                                        if (buffer[n] == 13 && buffer[n + 1] == 13) {
                                            count = read - (n + 2); // if so, set count to bytes read after the empty line
                                        }
                                        if (buffer[n] == 13 && buffer[n + 1] == 10 && buffer[n + 2] == 13 && buffer[n + 3] == 10) {
                                            count = read - (n + 4); // same as above
                                        }
                                    }
                                }
                            }

                            outputStream.write(buffer, 0, read);
                            if (inputStream.available() < 1) {
                                outputStream.flush();
                            }
                            if (length != null) {
                                length = length - read;
                            }
                        }
                    } while (read >= 0 && (length == null || length > 0) && (cLength == -1 || count < cLength));
                }

            } finally {
                if (!outputSocket.isOutputShutdown()) {
                    if (!isPost) {
                        outputSocket.shutdownOutput();
                    }
                }
            }
        } finally {
            if (!inputSocket.isInputShutdown()) {
                inputSocket.shutdownInput();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

虽然上面的代码目前不是100%正常工作,但它应该让您了解如何继续。如果内容长度为空且收到空行,您可能也会关闭流。