从这篇文章开始,我尝试实现一个处理GET
和POST
请求的小代理服务器(只需用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; ArrayListbuffer = new ArrayList (); 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
.
有没有人知道我做错了什么以及如何正确转发数据,以便所有浏览器正确加载内容而不会造成不必要的延迟?