HTTP Header中的数据:
Android客服端可以通过模拟HTTP的Post方式提交表单来上传本地图片到服务器端。
为了模拟Http提交表单,我们可以先来看看通过网页提交表单来请求服务器时,客户端提交了什么数据给服务端的(HTTP Header中都有什么内容)。
比如,提交的表单如下:
<form method="post" action="/upload/index" enctype="multipart/form-data">
<input type="text" name="name">
<input type="file" name="upload_image"/>
<input type="submit" name="ok"/>
</form>
点击提交表单之后,我们可以利用抓包工具来查看客户端提交给服务器端的内容。
下图是用Chrome抓取到HTTP Header中的信息:
现在知道了服务器端需要的是什么样格式的数据,那么就可以模拟这样的一组数据并提交的服务器端,这样android客户端就能实现像Web页面的表单提交了。
还有,就是写代码的时候要小心一点,不要漏了换行,少了或者多了字符什么的,这样可能导致请求失败。
Andoid端和服务器端的代码实现:
android客户端代码(封装成工具类):
HttpFormFIle类:
import java.io.File;
/**
* Http 表单文件参数
*
* 比如:
* <input type="file" name="upload_image"/>
* <input type="file" name="upload_zip"/>
*
* @author happen
*
*/
public class HttpFormFile {
private File file;
/**
* 参数名
*/
private String paramName;
/**
* 内容类型
*/
private String contentType = "application/octet-stream";
public HttpFormFile(String filePath, String paramName, String contentType) {
this(paramName, contentType);
file = new File(filePath);
}
public HttpFormFile(String paramName, File file, String contentType) {
this(paramName, contentType);
this.file = file;
}
private HttpFormFile(String paramName, String contentType) {
this.paramName = paramName;
setContentType(contentType);
}
/**
* 得到文件字节大小
* @return long 文件字节大小
*/
public long length() {
return file.length();
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public String getParamName() {
return paramName;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public void setFile(String fileName) {
this.file = new File(fileName);
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = (contentType == null ? this.contentType : contentType);
}
}
HttpRequestForm类:
import java.util.Map;
/**
* Http表单
* 包含文本类型参数和文件类型参数
* @author happen
*/
public class HttpRequestForm {
/**
* 表单文本类型参数
*/
private Map<String, String> textParams;
/**
* 表单文件类型参数
*/
private HttpFormFile[] fileParams;
public HttpRequestForm(Map<String, String> textParams) {
this.textParams = textParams;
}
public HttpRequestForm(HttpFormFile[] fileParams) {
this.fileParams = fileParams;
}
public HttpRequestForm(HttpFormFile fileParams) {
this(new HttpFormFile[] {fileParams});
}
public HttpRequestForm(Map<String, String> textParams, HttpFormFile[] fileParams) {
this.textParams = textParams;
this.fileParams = fileParams;
}
public HttpRequestForm(Map<String, String> textParams, HttpFormFile fileParams) {
this(textParams, new HttpFormFile[] {fileParams});
}
public Map<String, String> getTextParams() {
return textParams;
}
public void setTextParams(Map<String, String> textParams) {
this.textParams = textParams;
}
public HttpFormFile[] getFileParams() {
return fileParams;
}
public void setFileParams(HttpFormFile[] fileParams) {
this.fileParams = fileParams;
}
}
SocketHttpRequestUtils类:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Map;
/**
* 模拟Http协议提交请求,主要是拿来上传文件到服务器用的
* 普通Http请求可以用API
*
* 比如可以模拟提交如下表单:
* <form method="post" action="http://192.168.1.101:8888/upload/doUpload" enctype="multipart/form-data">
* <input type="text" name="username">
* <input type="text" name="id">
* <input type="file" name="upload_image"/>
* <input type="file" name="upload_zip"/>
* </form>
*
* @author happen
*/
public class SocketHttpRequestUtils {
public static final String TAG = "SocketHttpRequestUtils";
/**
* 数据分割线
*/
private static final String SET_BOUNDARY = "---------------------------7da2137580612";
private static final String DATA_BOUNDARY = "-----------------------------7da2137580612";
private static final String DATA_END_LINE = "-----------------------------7da2137580612--";
/**
* Http Post方式请求
* @param urlPath
* @param requestForm
* @return
* @throws UnknownHostException
* @throws IOException
*/
public static boolean post(String urlPath, HttpRequestForm requestForm)
throws UnknownHostException, IOException {
URL url = new URL(urlPath);
int port = (url.getPort() == -1 ? 8080 : url.getPort());
Socket socket = new Socket(InetAddress.getByName(url.getHost()), port);
OutputStream os = socket.getOutputStream();
// 写出HTTP Header
os.write(createHttpHeader(url, requestForm).getBytes());
// 写出文本参数
os.write(createTextParamsInfo(requestForm.getTextParams()).getBytes());
// 写出文件参数和文件数据
int len = 0;
byte[] buffer = new byte[1024];
for (HttpFormFile formFile : requestForm.getFileParams()) {
InputStream is;
// 写出文件信息
os.write(createFileParamInfo(formFile).getBytes());
// 写出文件数据
is = new FileInputStream(formFile.getFile());
while ((len = is.read(buffer, 0, 1024)) != -1) {
os.write(buffer, 0, len);
}
is.close();
// 写出换行
os.write("\r\n".getBytes());
}
// 写出结尾数据分割线
os.write((DATA_END_LINE + "\r\n").getBytes());
// 处理服务器响应
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 读取web服务器返回的数据,判断请求码是否为200,如果不是200,代表请求失败
if(br.readLine().indexOf("200") == -1) {
return false;
}
// 关闭打开的流
os.flush();
os.close();
br.close();
socket.close();
return true;
}
/**
* 生成请求信息
* @param textParams
* @return String
*/
private static String createHttpHeader(URL url, HttpRequestForm requestForm) {
StringBuilder httpHeader = new StringBuilder();
long contentLength = getContentLength(requestForm);
int port = (url.getPort() == -1 ? 8080 : url.getPort());
// Request Method
httpHeader.append("POST " + url.getPath() + " HTTP/1.1\r\n");
// Accept
httpHeader.append("Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg"
+ ", application/x-shockwave-flash, application/xaml+xml"
+ ", application/vnd.ms-xpsdocument, application/x-ms-xbap"
+ ", application/x-ms-application, application/vnd.ms-excel"
+ ", application/vnd.ms-powerpoint, application/msword, */*\r\n");
httpHeader.append("Accept-Language: zh-CN\r\n");
// ContentType,Boundary
// 注意:boundary前面少2个--
httpHeader.append("Content-Type: multipart/form-data; boundary=" + SET_BOUNDARY + "\r\n");
// Content-Length
httpHeader.append("Content-Length: "+ contentLength + "\r\n");
// Connection
httpHeader.append("Connection: Keep-Alive\r\n");
// Host
httpHeader.append("Host: "+ url.getHost() + ":" + port +"\r\n\r\n");
return httpHeader.toString();
}
/**
* 生成文本类型参数的模拟信息
* @param textParams
* @return String
*/
private static String createTextParamsInfo(Map<String, String> textParams) {
StringBuilder textInfoEntity = new StringBuilder();
// 得到所有文本类型参数
for (Map.Entry<String, String> param : textParams.entrySet()) {
textInfoEntity.append(DATA_BOUNDARY + "\r\n");
textInfoEntity.append("Content-Disposition: form-data; name=\""+ param.getKey() + "\"\r\n");
textInfoEntity.append("\r\n");
textInfoEntity.append(param.getValue() + "\r\n");
}
return textInfoEntity.toString();
}
/**
* 生成文件类型参数的的模拟信息,这里没用包含文件数据
* @param formFile
* @return String
*/
private static String createFileParamInfo(HttpFormFile formFile) {
StringBuilder fileInfoEntity = new StringBuilder();
fileInfoEntity.append(DATA_BOUNDARY + "\r\n");
fileInfoEntity.append("Content-Disposition: form-data; name=\"" + formFile.getParamName() + "\"; ");
fileInfoEntity.append("filename=\""+ formFile.getFile().getName() + "\"\r\n");
fileInfoEntity.append("Content-Type: "+ formFile.getContentType() + "\r\n\r\n");
return fileInfoEntity.toString();
}
/**
* 得到整个提交表单数据的长度
* @param requestForm
* @return String
*/
private static long getContentLength(HttpRequestForm requestForm) {
long length;
// 文本表单
length = createTextParamsInfo(requestForm.getTextParams()).getBytes().length;
// 文件表单和文件数据大小
for (HttpFormFile formFile : requestForm.getFileParams()) {
length += createFileParamInfo(formFile).getBytes().length;
length += formFile.length();
}
// 结尾数据分割线
length += (DATA_END_LINE + "\r\n").getBytes().length;
return length;
}
}
Demo:MainActivity类
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import com.zhp.upload.util.HttpFormFile;
import com.zhp.upload.util.HttpRequestForm;
import com.zhp.upload.util.SocketHttpRequestUtils;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
// 需要求请求的url
private static final String REQUEST_URL = "http://192.168.1.160:8888/upload/doUpload";
private File file;
private Handler handler;
private HttpRequestForm form;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 从SD卡中拿文件
file = new File(Environment.getExternalStorageDirectory(), "test2.jpg");
// 创建文本类型的参数
Map<String, String> textparams = new HashMap<String, String>();
textparams.put("username", "zhphaha");
textparams.put("pwd", "hapenen");
textparams.put("hobby", "sport");
textparams.put("filename", file.getName());
// 创建文件类型的参数
HttpFormFile formFile[] = { new HttpFormFile("image", file, null) };
form = new HttpRequestForm(textparams, formFile);
try {
// 开始请求
SocketHttpRequestUtils.post(REQUEST_URL, form);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
handler=new Handler();
handler.post(new UploadRunnable());
}
class UploadRunnable implements Runnable {
@Override
public void run() {
boolean uploadStatus = false;
Log.i(TAG, "Begin upload...");
try {
// 开始请求
uploadStatus = SocketHttpRequestUtils.post(REQUEST_URL, form);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(MainActivity.this, "upload status: " + uploadStatus, Toast.LENGTH_SHORT).show();
}
};
}
通过上面封装的类在控制台能输出HTTP Header的信息如下:
POST /test/upload/indexs.do HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-CN
Content-Type: multipart/form-data; boundary=---------------------------7da2137580612
Content-Length: 3201
Connection: Keep-Alive
Host: 192.168.1.160:8888
-----------------------------7da2137580612
Content-Disposition: form-data; name="fileName"
test2.jpg
-----------------------------7da2137580612
Content-Disposition: form-data; name="pwd"
happen
-----------------------------7da2137580612
Content-Disposition: form-data; name="username"
zhphaha
-----------------------------7da2137580612
Content-Disposition: form-data; name="hobby"
sport
Content-Disposition: form-data; name="image"; filename="test2.jpg"
Content-Type: application/octet-stream
/********* 这里是文件数据,由于是乱码没贴上来 *********/
-----------------------------7da2137580612--
服务器端代码(我这里使用struts):
我这里struts需要引用的包如下:
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-lang-2.4.jar
commons-lang-3.3.jar
commons-logging-1.1.jar
freemarker-2.3.13.jar
javassist-3.11.0.GA.jar
ognl-3.0.6.jar
struts2-core-2.3.8.jar
xwork-core-2.3.8.jar
strtus.xml配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<!-- 该属性指定需要Struts2处理的请求后缀,该属性的默认值是action,即所有匹配*.action的请求都由Struts2处理。
如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开。 -->
<!-- <constant name="struts.action.extension" value="do"/> -->
<!-- 设置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好关闭 -->
<constant name="struts.serve.static.browserCache" value="false"/>
<!-- 当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下使用),开发阶段最好打开 -->
<constant name="struts.configuration.xml.reload" value="true"/>
<!-- 开发模式下使用,这样可以打印出更详细的错误信息 -->
<constant name="struts.devMode" value="true"/>
<!-- 默认的视图主题 -->
<constant name="struts.ui.theme" value="simple"/>
<!--<constant name="struts.objectFactory" value="spring" />-->
<!--解决乱码 -->
<constant name="struts.i18n.encoding" value="UTF-8"/>
<!-- 指定允许上传的文件最大字节数。默认值是2097152(2M) -->
<constant name="struts.multipart.maxSize" value="22097152"/>
<!-- 设置上传文件的临时文件夹,默认使用javax.servlet.context.tempdir -->
<!-- <constant name="struts.multipart.saveDir " value="/UploadTmp"/> -->
<package name="/" namespace="/" extends="struts-default">
<action name="*" class="com.zhp.upload.action.UploadAction" method="{1}">
<param name="savePath">/images</param>
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
UploadAction类:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
* 上传文件例子
* @author happen
*
*/
@SuppressWarnings("serial")
public class UploadAction extends ActionSupport {
// 上传文件域
private File image;
// 上传文件类型
private String imageContentType;
// 封装上传文件名
private String imageFilename;
// 接受依赖注入的属性
private String savePath;
private String hobby;
private String username;
private String pwd;
private String filename;
public String doUpload() {
FileOutputStream fos = null;
FileInputStream fis = null;
byte[] buffer = new byte[1024];
int len = 0;
try {
System.out.println("来自android的表单文本类型数据:");
System.out.println("用户名:" + username);
System.out.println("密码:" + pwd);
System.out.println("兴趣爱好:" + hobby);
System.out.println("文件名:" + filename);
System.out.println("来自android的表单文件类型数据:");
System.out.println("文件存放目录: " + getSavePath());
System.out.println("文件名称: " + imageFilename);
System.out.println("文件大小: " + image.length());
System.out.println("文件类型: " + imageContentType);
fos = new FileOutputStream(getSavePath() + "/" + getImageFileName());
fis = new FileInputStream(getImage());
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("文件上传成功");
} catch (Exception e) {
System.out.println("文件上传失败");
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
fis = null;
} catch (IOException e) {
System.out.println("FileInputStream关闭失败");
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
fis = null;
} catch (IOException e) {
System.out.println("FileOutputStream关闭失败");
e.printStackTrace();
}
}
}
return SUCCESS;
}
/**
* 文件存放目录
* @return
*/
public String getSavePath() throws Exception{
return ServletActionContext.getServletContext().getRealPath(savePath);
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public File getImage() {
return image;
}
public void setImage(File image) {
this.image = image;
}
public String getImageContentType() {
return imageContentType;
}
public void setImageContentType(String imageContentType) {
this.imageContentType = imageContentType;
}
public String getImageFileName() {
return imageFilename;
}
public void setImageFileName(String imageFileName) {
this.imageFilename = imageFileName;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}
OK,记得android要写正确的请求url地址。 运行demo,服务器端输出如下:
来自android的表单文本类型数据:
用户名:zhphaha
密码:hapenen
兴趣爱好:sport
文件名:test2.jpg
来自android的表单文件类型数据:
文件存放目录: /home/happen/dev_tool/apache-tomcat-6.0.37/webapps/test/images
文件名称: test2.jpg
文件大小: 2584
文件类型: application/octet-stream
文件上传成功
以上包含了android和服务器端的代码实现。 如果只是简单地请求,我们还是用HttpPost或者是HttpGet吧。