从this post开始我尝试实现一个小型代理服务器来处理 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;
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
的这种阻塞调用。
有谁知道我做错了什么以及我如何正确转发数据,以便所有浏览器正确加载内容而不会造成不必要的延迟?
最佳答案
看,问题是您正在使用 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% 有效,但它应该可以让您了解如何继续。如果内容长度为空并且收到一个空行,您可能也关闭了流。
关于Java 代理 - 无法正确交换来自 HTTP GET/POST 请求的数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34159052/