在网络上所有的通讯交流都是通过使用一系列协议完成的,比如FTP,SMTP,POP,HTTP等等。HTTP是其中一个最流行的协议,它和WWW 是一个整体。它也是如今许多应用程序必须了解和使用的协议。如果一个java应用程序要使用HTTP进行交互,Comons HttpClient 组件可以使你的开发更加轻松。使用这个组件,你不必担心HTTP协议的所有技术问题,你只需要关心如何将HttpClient提供的许多类和方法和你的程 序整合在一起。在本篇文章中,你会看到HttpClient的功能和一些例子。
你也会大致了解一下FileUpload组件,它把服务器端文件上传工作简化了,你会看到一个HttpClient和FileUpload整合的例子。
注意 本文所有在服务器端的例子,我都是使用的Tomcat 4.0.6,尽管如此,当你使用了其他支持servlet和JSP的服务器,也应该不会有什么问题
表 9-1 展示了本文中涉及的组件细节
表 9-1. 组件细节
名称 | 版本 | 包 |
HttpClient | 2.0-rc1 | org.apache.commons.httpclient |
FileUpload | 1.0 | org.apache.commons.fileupload |
介绍HttpClient
HttpClient是一个致力于提供简单的程序编程接口(API),使Java开发人员能在HTTP之上编写代码。入伙你在开发一个Web浏览器 或者仅仅是一个偶尔需要一些来在Web的数据,HttpClient能帮助你在HTTP之上开发客户端代码。从名字上我们也可以看出, HttpClient意味着仅仅是HTTP客户端代码,并不能开发服务器端以处理HTTP请求。
我推荐你使用 HttpClient 代替 java.net 类包,因为HttpClient更容易使用,它支持一些 java.net 没有提供HTTP功能,并且还有一个活跃的社团支持它。你可以浏览http://www.nogoop.com/product_16.html#compare,比较一下HttpClient, java.net,和其它类似的API。
使用大量的公共组件时,Javadocs似乎是唯一真正存在的文档 。但是HttpClient在Javadocs之外还存在一些好文档。一个简短的教程在http://jakarta.apache.org/commons/httpclient/tutorial.html ,你可以开始HttpClient之旅了。
以下是HttpClient的一些重要特性
- 实现了HTTP 1.0 和 1.1
- 实现了所有HTTP方法,例如GET,POST,PUT,DELETE,HEAD,OPTIONS和TRACE
- 支持HTTPS和HTTP代理服务
- 支持Basic,Digest和NT LAN Manager(NTLM)身份验证
- 处理cookies
下面你会看到HttpClient的各种原理以及他们是如何让你在HTTP之上进行交流的。
使用HttpClient
HttpClient使用了Commons Logging组件,因此让HttpClient正常工作的唯一条件是 commons-logging 组件JAR文件必须存在。使用HttpClient 去处理大量需求是相当兼得的。你仅仅需要理解几个关键的类和接口。下面一节将介绍一个发送GET请求的简单例子,然后解释这些类是如何使例子运转起来的。
使用GET方法
GET方法是发送HTTP请求最普遍的方法。每次你点击一个超级链接,你就是使用GET方法发送了一个HTTP请求。下面你会看到一个使用HttpCilent发送GET请求的例子。在Listing 9-1 代码中,发送一个GET请求到这个URL地址http://localhost:8080/validatorStrutsApp/userInfo.do,并且包含有三个请求参数firstname,lastname和email.
Listing 9-1. HttpClientTrial
package com.commonsbook.chap9;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
public class SubmitHttpForm {
private static String url =
"http://localhost:8080/validatorStrutsApp/userInfo.do";
public static void main(String[] args) {
//Instantiate an HttpClient
HttpClient client = new HttpClient();
//Instantiate a GET HTTP method
HttpMethod method = new GetMethod(url);
//Define name-value pairs to set into the QueryString
NameValuePair nvp1= new NameValuePair("firstName","fname");
NameValuePair nvp2= new NameValuePair("lastName","lname");
NameValuePair nvp3= new NameValuePair("email","email@email.com");
method.setQueryString(new NameValuePair[]{nvp1,nvp2, nvp3});
try{
int statusCode = client.executeMethod(method);
System.out.println("QueryString>>> "+method.getQueryString());
System.out.println("Status Text>>>"
+HttpStatus.getStatusText(statusCode));
//Get data as a String
System.out.println(method.getResponseBodyAsString());
//OR as a byte array
byte [] res = method.getResponseBody();
//write to file
FileOutputStream fos= new FileOutputStream("donepage.html");
fos.write(res);
//release connection
method.releaseConnection();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
The output on executing this piece of code will depend on the response you get to your GET request.
The following steps take place in the class SubmitHttpForm to invoke the URL specified, including passing the three parameters as part of the query string, displaying the response, and writing the response to a file:
- You first need to instantiate the HttpClient, and because you have specified no parameters to the constructor, by default the org.apache.commons.httpclient.SimpleHttpConnectionManager class is used to create a new HttpClient. To use a different ConnectionManager, you can specify any class implementing the interface org.apache.commons.httpclient.HttpConnectionManager as a parameter to the constructor. You can use the MultiThreadedHttpConnectionManager connection manager if more than one thread is likely to use the HttpClient. The code would then be new HttpClient(new MultiThreadedHttpConnectionManager()).
- Next you create an instance of HttpMethod. Because HttpClient provides implementations for all HTTP methods, you could very well have chosen an instance of PostMethod instead of GetMethod. Because you are using an HttpMethod reference and not a reference of an implementation class such as GetMethod or PostMethod, you intend to use no special features provided by implementations such as GetMethod or PostMethod.
- You define name/value pairs and then set an array of those name/value pairs into the query string.
- Once the groundwork is complete, you execute the method using the HttpClient instance you created in step 1. The response code returned is based on the success or failure of the execution.
- You get the response body both as a string and as a byte array. The response is printed to the console and also written to a file named donepage.html.
NOTE The class org.apache.commons.httpclient.HttpStatus defines static int variables that map to HTTP status codes.
In this example, you can see how easily you can fire a request and get a response over HTTP using the HttpClient component. You might have noted that writing such code has a lot of potential to enable testing of Web applications quickly and to even load test them. This has led to HttpClient being used in popular testing framework such as Jakarta Cactus, HTMLUnit, and so on. You can find in the documentation a list of popular applications that use HttpClient.
You used the GET method to send name/value pairs as part of a request. However, the GET method cannot always serve your purpose, and in some cases using the POST method is a better option.
使用POST方法
Listing 9-2 展示了一个封装了XML文件的请求并使用POST方法发送给一个名为GetRequest.jsp 的JSP文件的例子 JSP会打出它接收的请求文件头。如果请求能够到达,这些文件头会被正确地显示出来。
Listing 9-2. Sending an XML File Using the POST Method
package com.commonsbook.chap9;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class PostAFile {
private static String url =
"http://localhost:8080/HttpServerSideApp/GetRequest.jsp";
public static void main(String[] args) throws IOException {
HttpClient client = new HttpClient();
PostMethod postMethod = new PostMethod(url);
client.setConnectionTimeout(8000);
// Send any XML file as the body of the POST request
File f = new File("students.xml");
System.out.println("File Length = " + f.length());
postMethod.setRequestBody(new FileInputStream(f));
postMethod.setRequestHeader("Content-type",
"text/xml; charset=ISO-8859-1");
int statusCode1 = client.executeMethod(postMethod);
System.out.println("statusLine>>>" + postMethod.getStatusLine());
postMethod.releaseConnection();
}
}
In Listing 9-2, I have stated the URL for GetRequest.jsp using a server I am running locally on port 8080. This URL will vary based on the server where the JSP is being maintained. In this example, you create an instance of the classes HttpClient and PostMethod. You set the connection timeout for the HTTP connection to 3,000 milliseconds and then set an XML file into the request body. I am using a file named students.xml however, the contents of the file are not relevant to the example, and you could very well use any other file. Because you are sending an XML file, you also set the Content-Type header to state the format and the character set. GetRequest.jsp contains only a scriptlet that prints the request headers. The contents of the JSP are as follows:
<%
java.util.Enumeration e= request.getHeaderNames();
while (e.hasMoreElements()) {
String headerName=(String)e.nextElement();
System.out.println(headerName +" = "+request.getHeader(headerName));
}
%>
Upon executing the class PostAFile, the JSP gets invoked, and the output displayed on the server console is as follows:
content-type = text/xml; charset=ISO-8859-1
user-agent = Jakarta Commons-HttpClient/2.0rc1
host = localhost:8080
content-length = 279
The output shown on the console where the PostAFile class was executed is as follows:
File Length = 279
statusLine>>>HTTP/1.1 200 OK
Note that the output on the server shows the content length as 279 (bytes), the same as the length of the file students.xml that is shown on the application console. Because you are not invoking the JSP using any browser, the User-Agent header that normally states the browser specifics shows the HttpClient version being used instead.
NOTE In this example, you sent a single file over HTTP. To upload multiple files, the MultipartPostMethod class is a better alternative. You will look at it later in the “Introducing FileUpload” section.
管理Cookies
HttpClient provides cookie management features that can be particularly useful to test the way an application handles cookies. Listing 9-3 shows an example where you use HttpClient to add a cookie to a request and also to list details of cookies set by the JSP you invoke using the HttpClient code.
The HttpState class plays an important role while working with cookies. The HttpState class works as a container for HTTP attributes such as cookies that can persist from one request to another. When you normally surf the Web, the Web browser is what stores the HTTP attributes.
Listing 9-3. CookiesTrial.java
package com.commonsbook.chap9;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
public class CookiesTrial {
private static String url =
"http://127.0.0.1:8080/HttpServerSideApp/CookieMgt.jsp";
public static void main(String[] args) throws Exception {
//A new cookie for the domain 127.0.0.1
//Cookie Name= ABCD Value=00000 Path=/ MaxAge=-1 Secure=False
Cookie mycookie = new Cookie("127.0.0.1", "ABCD", "00000", "/", -1, false);
//Create a new HttpState container
HttpState initialState = new HttpState();
initialState.addCookie(mycookie);
//Set to COMPATIBILITY for it to work in as many cases as possible
initialState.setCookiePolicy(CookiePolicy.COMPATIBILITY);
//create new client
HttpClient httpclient = new HttpClient();
//set the HttpState for the client
httpclient.setState(initialState);
GetMethod getMethod = new GetMethod(url);
//Execute a GET method
int result = httpclient.executeMethod(getMethod);
System.out.println("statusLine>>>"+getMethod.getStatusLine());
//Get cookies stored in the HttpState for this instance of HttpClient
Cookie[] cookies = httpclient.getState().getCookies();
for (int i = 0; i < cookies.length; i++) {
System.out.println("/nCookieName="+cookies[i].getName());
System.out.println("Value="+cookies[i].getValue());
System.out.println("Domain="+cookies[i].getDomain());
}
getMethod.releaseConnection();
}
}
In Listing 9-3, you use the HttpState instance to store a new cookie and then associate this instance with the HttpClient instance. You then invoke CookieMgt.jsp. This JSP is meant to print the cookies it finds in the request and then add a cookie of its own. The JSP code is as follows:
<%
Cookie[] cookies= request.getCookies();
for (int i = 0; i < cookies.length; i++) {
System.out.println(cookies[i].getName() +" = "+cookies[i].getValue());
}
//Add a new cookie
response.addCookie(new Cookie("XYZ","12345"));
%>
CAUTION HttpClient code uses the class org.apache.commons.httpclient.Cookie, and JSP and servlet code uses the class javax.servlet.http.Cookie.
The output on the application console upon executing the CookiesTrial class and invoking CookieMgt.jsp is as follows:
statusLine>>>HTTP/1.1 200 OK
CookieName=ABCD
value="/00000
Domain=127.0.0.1
CookieName=XYZ
Value=12345
Domain=127.0.0.1
CookieName=JSESSIONID
Value=C46581331881A84483F0004390F94508
Domain=127.0.0.1
In" this output, note that although the cookie named ABCD has been created from CookiesTrial, the other cookie named XYZ is the one inserted by the JSP code. The cookie named JSESSIONID is meant for session tracking and gets created upon invoking the JSP. The output as displayed on the console of the server when the JSP is executed is as follows:
ABCD = 00000
This shows that when CookieMgt.jsp receives the request from the CookiesTrial class, the cookie named ABCD was the only cookie that existed. The sidebar “HTTPS and Proxy Servers” shows how you should handle requests over HTTPS and configure your client to go through a proxy.
|
You will now see the HttpClient component’s capability to use MultipartPostMethod to upload multiple files. You will look at this in tandem with the Commons FileUpload component. This Commons component is specifically meant to handle the server-side tasks associated with file uploads.
介绍FileUpload
The FileUpload component has the capability of simplifying the handling of files uploaded to a server. Note that the FileUpload component is meant for use on the server side; in other words, it handles where the files are being uploaded to—not the client side where the files are uploaded from. Uploading files from an HTML form is pretty simple; however, handling these files when they get to the server is not that simple. If you want to apply any rules and store these files based on those rules, things get more difficult.
The FileUpload component remedies this situation, and in very few lines of code you can easily manage the files uploaded and store them in appropriate locations. You will now see an example where you upload some files first using a standard HTML form and then using HttpClient code.
使用HTML File Upload
The commonly used methodology to upload files is to have an HTML form where you define the files you want to upload. A common example of this HTML interface is the Web page you encounter when you want to attach files to an email while using any of the popular Web mail services.
In this example, you will create a simple HTML page where you provide for three files to be uploaded. Listing 9-4 shows the HTML for this page. Note that the enctype attribute for the form has the value multipart/form-data, and the input tag used is of type file. Based on the value of the action attribute, on form submission, the data is sent to ProcessFileUpload.jsp.
Listing 9-4. UploadFiles.html
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252"/>
<TITLE>File Upload Page</TITLE>
</HEAD>
<BODY>Upload Files
<FORM name="filesForm" action="ProcessFileUpload.jsp"
method="post" enctype="multipart/form-data">
File 1:<input type="file" name="file1"/><br/>
File 2:<input type="file" name="file2"/><br/>
File 3:<input type="file" name="file3"/><br/>
<input type="submit" name="Submit" value="Upload Files"/>
</FORM>
</BODY>
</HTML>
You can use a servlet to handle the file upload. I have used JSP to minimize the code you need to write. The task that the JSP has to accomplish is to pick up the files that are sent as part of the request and store these files on the server. In the JSP, instead of displaying the result of the upload in the Web browser, I have chosen to print messages on the server console so that you can use this same JSP when it is not invoked through an HTML form but by using HttpClient-based code.
Listing 9-5 shows the JSP code. Note the code that checks whether the item is a form field. This check is required because the Submit button contents are also sent as part of the request, and you want to distinguish between this data and the files that are part of the request. You have set the maximum file size to 1,000,000 bytes using the setSizeMax method.
Listing 9-5. ProcessFileUpload.jsp
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ page import="org.apache.commons.fileupload.DiskFileUpload"%>
<%@ page import="org.apache.commons.fileupload.FileItem"%>
<%@ page import="java.util.List"%>
<%@ page import="java.util.Iterator"%>
<%@ page import="java.io.File"%>
html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>Process File Upload</title>
</head>
<%
System.out.println("Content Type ="+request.getContentType());
DiskFileUpload fu = new DiskFileUpload();
// If file size exceeds, a FileUploadException will be thrown
fu.setSizeMax(1000000);
List fileItems = fu.parseRequest(request);
Iterator itr = fileItems.iterator();
while(itr.hasNext()) {
FileItem fi = (FileItem)itr.next();
//Check if not form field so as to only handle the file inputs
//else condition handles the submit button input
if(!fi.isFormField()) {
System.out.println("/nNAME: "+fi.getName());
System.out.println("SIZE: "+fi.getSize());
//System.out.println(fi.getOutputStream().toString());
File fNew= new File(application.getRealPath("/"), fi.getName());
System.out.println(fNew.getAbsolutePath());
fi.write(fNew);
}
else {
System.out.println("Field ="+fi.getFieldName());
}
}
%>
<body>
Upload Successful!!
</body>
</html>
注意 With FileUpload 1.0 I found that when the form was submitted using Opera version 7.11, the getName method of the class FileItem returns just the name of the file. However, if the form is submitted using Internet Explorer 5.5, the filename along with its entire path is returned by the same method. This can cause some problems.
To run this example, you can use any three files, as the contents of the files are not important. Upon submitting the form using Opera and uploading three random XML files, the output I got on the Tomcat server console was as follows:
Content Type =multipart/form-data; boundary=----------rz7ZNYDVpN1To8L73sZ6OE
NAME: academy.xml
SIZE: 951
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/academy.xml
NAME: academyRules.xml
SIZE: 1211
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/academyRules.xml
NAME: students.xml
SIZE: 279
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/students.xml
Field =Submit
However, when submitting this same form using Internet Explorer 5.5, the output on the server console was as follows:
Content Type =multipart/form-data; boundary=---------------------------7d3bb1de0
2e4
NAME: D:/temp/academy.xml
SIZE: 951
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/D:/temp/academy.xml
The browser displayed the following message: “The requested resource (D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/D:/temp/academy.xml (The filename, directory name, or volume label syntax is incorrect)) is not available.”
This contrasting behavior on different browsers can cause problems. One workaround that I found in an article at http://www.onjava.com/pub/a/onjava/2003/06/25/commons.html is to first create a file reference with whatever is supplied by the getName method and then create a new file reference using the name returned by the earlier file reference. Therefore, you can insert the following code to have your code work with both browsers (I wonder who the guilty party is…blaming Microsoft is always the easy way out)
File tempFileRef = new File(fi.getName());
File fNew = new File(application.getRealPath("/"),tempFileRef.getName());
In this section, you uploaded files using a standard HTML form mechanism. However, often a need arises to be able to upload files from within your Java code, without any browser or form coming into the picture. In the next section, you will look at HttpClient-based file upload.
使用基于HttpClient的FileUpload
Earlier in the article you saw some of the capabilities of the HttpClient component. One capability I did not cover was its ability to send multipart requests. In this section, you will use this capability to upload a few files to the same JSP that you used for uploads using HTML.
The class org.apache.commons.httpclient.methods.MultipartPostMethod provides the multipart method capability to send multipart-encoded forms, and the package org.apache.commons.httpclient.methods.multipart has the support classes required. Sending a multipart form using HttpClient is quite simple. In the code in Listing 9-6, you send three files to ProcessFileUpload.jsp.
Listing 9-6. HttpMultiPartFileUpload.java
package com.commonsbook.chap9;
import java.io.File;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.MultipartPostMethod;
public class HttpMultiPartFileUpload {
private static String url =
"http://localhost:8080/HttpServerSideApp/ProcessFileUpload.jsp";
public static void main(String[] args) throws IOException {
HttpClient client = new HttpClient();
MultipartPostMethod mPost = new MultipartPostMethod(url);
client.setConnectionTimeout(8000);
// Send any XML file as the body of the POST request
File f1 = new File("students.xml");
File f2 = new File("academy.xml");
File f3 = new File("academyRules.xml");
System.out.println("File1 Length = " + f1.length());
System.out.println("File2 Length = " + f2.length());
System.out.println("File3 Length = " + f3.length());
mPost.addParameter(f1.getName(), f1);
mPost.addParameter(f2.getName(), f2);
mPost.addParameter(f3.getName(), f3);
int statusCode1 = client.executeMethod(mPost);
System.out.println("statusLine>>>" + mPost.getStatusLine());
mPost.releaseConnection();
}
}
In this code, you just add the files as parameters and execute the method. The ProcessFileUpload.jsp file gets invoked, and the output is as follows:
Content Type =multipart/form-data; boundary=----------------31415926535897932384
6
NAME: students.xml
SIZE: 279
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/students.xml
NAME: academy.xml
SIZE: 951
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/academy.xml
NAME: academyRules.xml
SIZE: 1211
D:/javaGizmos/jakarta-tomcat-4.0.1/webapps/HttpServerSideApp/academyRules.xml
Thus, file uploads on the server side become quite a simple task if you are using the Commons FileUpload component.
总结
通过本文,你已经了解了HttpClient和FileUpload组件。尽管HttpClient在许多使用HTTP进行交流的应用程序中很有 用,但是FileUpload组件还有更广阔的使用空间。还有一点需要说明的是,HttpClient有相当好的用户手册和教程。如果你正苦恼通过你的应 用程序如何使用和管理上传的文件,那么FileUpload是你正在寻找的组件。