[android]模拟Http表单,实现本地文件(图片等)上传到服务器端

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吧。




  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值