通过 Java Servlet 发送 XML 数据时发送了不需要的字符
Unwanted characters sent while sending XML data via Java Servlets
我一直在开发一个 Java 网络应用程序,它仅通过 HTML
形式获取 first_name
、middle_name
和 last_name
参数,然后将其嵌入数据到 XML 文件中并返回给客户端。
我设置了Content-Type: text/xml
。
这是我的 servlet 代码:
package com.adi.request.xml;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RequestToXMLServlet extends HttpServlet {
private String lastName;
private String firstName;
private String middleName;
/* Request Handling... */
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
setName(request); // Initialising the firstName, middleName and lastName
String xmlDoc = getXML(); // Build and recieve the XML output
response.setContentType("text/xml"); // TO BE NOTED...
try(PrintWriter writer = response.getWriter()) {
writer.print(xmlDoc); // Printing the XML output
writer.flush();
} catch(IOException e) {
e.printStackTrace();
}
}
// Setting the firstName, middleName and lastName
private void setName(HttpServletRequest request) {
firstName = request.getParameter("first_name");
lastName = request.getParameter("last_name");
middleName = request.getParameter("middle_name");
}
// Building the XML output
private String getXML() {
// The append() methods just adds a \r\n at the end of every line.
String xmlDoc = append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")+
append("<Request>")+
append(" <FirstName>"+firstName+"</FirstName>")+
append(" <MiddleName>"+middleName+"</MiddleName>")+
append(" <LastName>"+lastName+"</LastName>")+
append("</Request>");
return xmlDoc;
}
private String append(String str) {
return str + "\r\n";
}
}
HTML形式:
<!DOCTYPE html>
<html>
<head>
<title>Request to XML - Servlet</title>
</head>
<body>
<form method="GET" action="Request.do">
<label for="first_name">Firstname:</label>
<input type="text" name="first_name" id="first_name" />
<br>
<label for="middle_name">Middlename</Label>
<input type="text" name="middle_name" id="middle_name" />
<br>
<label for="last_name">Lastname</Label>
<input type="text" name="last_name" id="last_name" />
<br>
<input type="submit" name="submit" value="GET" />
</form>
</body>
</html>
这工作正常,我的浏览器正确显示 XML
格式的数据。
问题在于
我写了一个小 jython
应用程序,它使用原始套接字向上面写的 Java Servlet
发出 HTTP POST
请求。虽然它接收到正确的 XML 格式数据,但它也会在实际需要的 XML 数据的开头和结尾接收不需要的字符。
这是我的 jython
代码:
from java.io import *
from java.net import *
from java.util import *
sock = Socket("localhost", 8080)
ostream = sock.getOutputStream()
writer = PrintWriter(ostream)
params="first_name=Aditya&middle_name=Rameshwarpratap&last_name=Singh"
writer.print("GET /RequestToXML/Request.do?"+params+" HTTP/1.1\r\n")
writer.print("Host: localhost:8080\r\n")
writer.print("Connection: Close\r\n")
writer.print("\r\n")
writer.flush()
istream = sock.getInputStream()
scanner = Scanner(istream)
while(scanner.hasNextLine()):
print(scanner.nextLine())
istream.close()
ostream.close()
scanner.close()
writer.close()
sock.close()
这段代码的输出是:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=ISO-8859-1
Transfer-Encoding: chunked
Date: Thu, 16 Jul 2015 18:46:37 GMT
Connection: close
bc // What is this?
<?xml version="1.0" encoding="utf-8"?>
<Request type="POST">
<FirstName>Aditya</FirstName>
<MiddleName>Rameshwarpratap</MiddleName>
<LastName>Singh</LastName>
</Request>
0 // And this?
所以我的问题是:
这些字符是什么?为什么在内容类型为 text/xml
时还要发送这些字符?
这无关紧要,但在我的 jython 代码中,我已经在代码末尾关闭了所有流和套接字。是否有必要关闭所有这些或其中的一些可以完成清理工作?
这是十六进制的块长度。看,响应 body 正在按以下 header:
分块发送
有关此传输编码的更多详细信息,请参阅 Wikipedia。带有 bc
的行表示 188 字节长的块的开始 (0xBC = 188)。带有 0
的行表示终止块(因此客户端知道它可以停止读取并且不需要等待具有剩余内容的新块,以防连接设置为保持活动状态)。
当内容长度未知并且客户端已将自己标识为支持 HTTP 1.1 的客户端时,servletcontainer 将自动切换到分块编码。它甚至在 doGet()
:
的 javadoc 中明确提到
...
Where possible, set the Content-Length
header (with the ServletResponse.setContentLength(int)
method), to allow the servlet container to use a persistent connection to return its response to the client, improving performance. The content length is automatically set if the entire response fits inside the response buffer.
When using HTTP 1.1 chunked encoding (which means that the response has a Transfer-Encoding
header), do not set the Content-Length
header.
...
您的客户端未以能够使用分块响应的方式编写。它基本上是一个非常基本的套接字,在请求中 header 伪装成 HTTP 1.1 客户端。
如果无法负担重写客户端以使其能够处理它(至少尝试假装成 HTTP 1.0 客户端),或者切换到真正的 1.1 HTTP 感知客户端(在 Java 术语,例如 URLConnection
),然后以设置内容长度的方式重写您的 servlet。
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
String xmlDoc = getXML();
byte[] content = xmlDoc.getBytes("UTF-8");
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.setContentLengthLong(content.length);
response.getOutputStream().write(content);
}
如果你不是 Java EE 7 / Servlet 3.1 yet,你可以保证 XML 内容不大于 Integer.MAX_VALUE
(2GB ), 然后使用
response.setContentLength((int) content.length);
或者如果你不能保证,那么使用
response.setHeader("Content-Length", String.valueOf(content.length));
请注意,它必须代表字节长度,因此肯定不是字符(字符串)长度。另请注意,您不需要 try-with-resources 语句。容器会自己担心冲洗和关闭。
另请参阅:
- Java Servlet HttpResponse contentLenght Header
- Do I need to flush the servlet outputstream?
- Should I close the servlet outputstream?
与具体问题无关,您的 servlet 正在 per-request 基础上处理实例变量。这不是线程安全的。将这些实例变量移动到方法块内。有关详细信息,另请参阅 How do servlets work? Instantiation, sessions, shared variables and multithreading.
我一直在开发一个 Java 网络应用程序,它仅通过 HTML
形式获取 first_name
、middle_name
和 last_name
参数,然后将其嵌入数据到 XML 文件中并返回给客户端。
我设置了Content-Type: text/xml
。
这是我的 servlet 代码:
package com.adi.request.xml;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class RequestToXMLServlet extends HttpServlet {
private String lastName;
private String firstName;
private String middleName;
/* Request Handling... */
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
setName(request); // Initialising the firstName, middleName and lastName
String xmlDoc = getXML(); // Build and recieve the XML output
response.setContentType("text/xml"); // TO BE NOTED...
try(PrintWriter writer = response.getWriter()) {
writer.print(xmlDoc); // Printing the XML output
writer.flush();
} catch(IOException e) {
e.printStackTrace();
}
}
// Setting the firstName, middleName and lastName
private void setName(HttpServletRequest request) {
firstName = request.getParameter("first_name");
lastName = request.getParameter("last_name");
middleName = request.getParameter("middle_name");
}
// Building the XML output
private String getXML() {
// The append() methods just adds a \r\n at the end of every line.
String xmlDoc = append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")+
append("<Request>")+
append(" <FirstName>"+firstName+"</FirstName>")+
append(" <MiddleName>"+middleName+"</MiddleName>")+
append(" <LastName>"+lastName+"</LastName>")+
append("</Request>");
return xmlDoc;
}
private String append(String str) {
return str + "\r\n";
}
}
HTML形式:
<!DOCTYPE html>
<html>
<head>
<title>Request to XML - Servlet</title>
</head>
<body>
<form method="GET" action="Request.do">
<label for="first_name">Firstname:</label>
<input type="text" name="first_name" id="first_name" />
<br>
<label for="middle_name">Middlename</Label>
<input type="text" name="middle_name" id="middle_name" />
<br>
<label for="last_name">Lastname</Label>
<input type="text" name="last_name" id="last_name" />
<br>
<input type="submit" name="submit" value="GET" />
</form>
</body>
</html>
这工作正常,我的浏览器正确显示 XML
格式的数据。
问题在于
我写了一个小 jython
应用程序,它使用原始套接字向上面写的 Java Servlet
发出 HTTP POST
请求。虽然它接收到正确的 XML 格式数据,但它也会在实际需要的 XML 数据的开头和结尾接收不需要的字符。
这是我的 jython
代码:
from java.io import *
from java.net import *
from java.util import *
sock = Socket("localhost", 8080)
ostream = sock.getOutputStream()
writer = PrintWriter(ostream)
params="first_name=Aditya&middle_name=Rameshwarpratap&last_name=Singh"
writer.print("GET /RequestToXML/Request.do?"+params+" HTTP/1.1\r\n")
writer.print("Host: localhost:8080\r\n")
writer.print("Connection: Close\r\n")
writer.print("\r\n")
writer.flush()
istream = sock.getInputStream()
scanner = Scanner(istream)
while(scanner.hasNextLine()):
print(scanner.nextLine())
istream.close()
ostream.close()
scanner.close()
writer.close()
sock.close()
这段代码的输出是:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=ISO-8859-1
Transfer-Encoding: chunked
Date: Thu, 16 Jul 2015 18:46:37 GMT
Connection: close
bc // What is this?
<?xml version="1.0" encoding="utf-8"?>
<Request type="POST">
<FirstName>Aditya</FirstName>
<MiddleName>Rameshwarpratap</MiddleName>
<LastName>Singh</LastName>
</Request>
0 // And this?
所以我的问题是:
这些字符是什么?为什么在内容类型为
text/xml
时还要发送这些字符?这无关紧要,但在我的 jython 代码中,我已经在代码末尾关闭了所有流和套接字。是否有必要关闭所有这些或其中的一些可以完成清理工作?
这是十六进制的块长度。看,响应 body 正在按以下 header:
分块发送有关此传输编码的更多详细信息,请参阅 Wikipedia。带有 bc
的行表示 188 字节长的块的开始 (0xBC = 188)。带有 0
的行表示终止块(因此客户端知道它可以停止读取并且不需要等待具有剩余内容的新块,以防连接设置为保持活动状态)。
当内容长度未知并且客户端已将自己标识为支持 HTTP 1.1 的客户端时,servletcontainer 将自动切换到分块编码。它甚至在 doGet()
:
...
Where possible, set the
Content-Length
header (with theServletResponse.setContentLength(int)
method), to allow the servlet container to use a persistent connection to return its response to the client, improving performance. The content length is automatically set if the entire response fits inside the response buffer.When using HTTP 1.1 chunked encoding (which means that the response has a
Transfer-Encoding
header), do not set theContent-Length
header....
您的客户端未以能够使用分块响应的方式编写。它基本上是一个非常基本的套接字,在请求中 header 伪装成 HTTP 1.1 客户端。
如果无法负担重写客户端以使其能够处理它(至少尝试假装成 HTTP 1.0 客户端),或者切换到真正的 1.1 HTTP 感知客户端(在 Java 术语,例如 URLConnection
),然后以设置内容长度的方式重写您的 servlet。
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
// ...
String xmlDoc = getXML();
byte[] content = xmlDoc.getBytes("UTF-8");
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.setContentLengthLong(content.length);
response.getOutputStream().write(content);
}
如果你不是 Java EE 7 / Servlet 3.1 yet,你可以保证 XML 内容不大于 Integer.MAX_VALUE
(2GB ), 然后使用
response.setContentLength((int) content.length);
或者如果你不能保证,那么使用
response.setHeader("Content-Length", String.valueOf(content.length));
请注意,它必须代表字节长度,因此肯定不是字符(字符串)长度。另请注意,您不需要 try-with-resources 语句。容器会自己担心冲洗和关闭。
另请参阅:
- Java Servlet HttpResponse contentLenght Header
- Do I need to flush the servlet outputstream?
- Should I close the servlet outputstream?
与具体问题无关,您的 servlet 正在 per-request 基础上处理实例变量。这不是线程安全的。将这些实例变量移动到方法块内。有关详细信息,另请参阅 How do servlets work? Instantiation, sessions, shared variables and multithreading.