在C#客户端用HTTP上传文件到Java服务器
在C#客户端用HTTP上传文件到Java服务器
最近在做C / S 开发,需要在C#客户端上传文件到Java后台进行处理。
对于较大的文件我们可以直接用FTP协议传文件,较小的文件则可以向B / S 一样用HTTP上传。
首先,由于要传文件,我们需要用 POST 来发送数据。GET 有长度限制,而且数据跟在URL后面。
既然要发送POST请求,我们先来看看POST 请求的报文格式。
HTTP 报文介绍
先写一个简单的Html 页面发送一个表单来观察它发出的POST 报文,表单中包含一个上传的文件和文件描述的文本。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8" />
<title>文件上传</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="http://www.baidu.com/form">
<input type="file" name="file">
<input type="text" name="description"
<br />
<input type="reset" value="reset">
<input type="submit" value="submit">
</form>
</body>
</html>

在Chrom 上的报文格式如下:

POST /form HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Content-Length: 2417
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryM4LGQcTCCIBilnPT
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: BAIDUID=9A110F7F907AEAC501CD156DDE0EA380:FG=1
------WebKitFormBoundaryM4LGQcTCCIBilnPT
Content-Disposition: form-data; name="file"; filename="close.png"
Content-Type: image/png
这里包括了图片的二进制数据
------WebKitFormBoundaryM4LGQcTCCIBilnPT
Content-Disposition: form-data; name="description"
This is a image
------WebKitFormBoundaryM4LGQcTCCIBilnPT--

HTTP报文由三个部分组成:对报文进行描述的起始行(start line),包含属性的首部(header)块,以及可选的、包含数据的主体(body)部分。
请求报文的起始行格式为<method> <request-URL> <version>
POST /form HTTP/1.1
method :为客户端希望服务器对资源进行的动作,一般为GET、POST、HEAD等。
请求URL:为资源的绝对路径,这里是表单Action决定的。
版本:保报文所使用的Http 版本,如1.1 ,1.0。
HTTP 首部块
可以有零个或多个首部,每个首部都包含一个名字,后面跟着一个冒号( : ),然后是一个可选的空格,接着是一个值,最后是一个CRLF( /r/n )。首部是由一个空行(CRLF)结束的。表示了首部列表的结束和实体主体部分的开始。在自己构造报文时一定要注意加换行和空行,以免造成格式错误。在HTTP 1.1 中,要求有效的请求或响应中必须包含特定的首部。请求首部如下:

Host: www.baidu.com
Connection: keep-alive
Content-Length: 2417
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryM4LGQcTCCIBilnPT
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

简单解释一下几个首部:
Host:接收请求的服务器地址
Connection:允许客户端和服务器指定与请求/响应连接有关的选项,Keep-alive 表示持久连接
Content-Length:实体主体的大小,这个在构造报文的时候一定要设置
CacheControl:控制缓存的行为
Accept:用户代理可处理的媒体类型
User-Agent:HTTP客户端程序的信息,浏览器的信息
Content-Type:实体主体的媒体类型,表单中有文件上传应设置为multipart/form-data。boundary 很重要,这是一个识别文件流的边界,用来标识文件开始和结尾的位置。
Accept-Encoding:是浏览器发给服务器,声明浏览器支持的编码类型
Accept-Language声明浏览器支持的语言
HTTP 数据主体
这部分为HTTP要传输的内容。
开始的boundary 就是在Content-Type中设置的值,boundary用于作为请求参数之间的界限标识,在多个参数之间要有一个明确的界限,这样服务器才能正确的解析到参数。它有格式要求,开头必须是--,不同的浏览器产生的boundary也不同,但前面都要有-- 。
Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
中间的就是我们传输的数据了。
最后还要加上一个boundary--,不要忘记最后的--。
这样报文就构造结束了。
C# 中发送POST请求

private void UploadRequest(string url, string filePath)
{
// 时间戳,用做boundary
string timeStamp = DateTime.Now.Ticks.ToString("x"); //根据uri创建HttpWebRequest对象
HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(new Uri(url));
httpReq.Method = "POST";
httpReq.AllowWriteStreamBuffering = false; //对发送的数据不使用缓存
httpReq.Timeout = 300000; //设置获得响应的超时时间(300秒)
httpReq.ContentType = "multipart/form-data; boundary=" + timeStamp; //文件
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
BinaryReader binaryReader = new BinaryReader(fileStream); //头信息
string boundary = "--" + timeStamp;
string dataFormat = boundary + "\r\nContent-Disposition: form-data; name=\"{0}\";filename=\"{1}\"\r\nContent-Type:application/octet-stream\r\n\r\n";
string header = string.Format(dataFormat, "file", Path.GetFileName(filePath));
byte[] postHeaderBytes = Encoding.UTF8.GetBytes(header); //结束边界
byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + timeStamp + "--\r\n"); long length = fileStream.Length + postHeaderBytes.Length + boundaryBytes.Length; httpReq.ContentLength = length;//请求内容长度 try
{
//每次上传4k
int bufferLength = 4096;
byte[] buffer = new byte[bufferLength]; //已上传的字节数
long offset = 0;
int size = binaryReader.Read(buffer, 0, bufferLength);
Stream postStream = httpReq.GetRequestStream(); //发送请求头部消息
postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length); while (size > 0)
{
postStream.Write(buffer, 0, size);
offset += size;
size = binaryReader.Read(buffer, 0, bufferLength);
} //添加尾部边界
postStream.Write(boundaryBytes, 0, boundaryBytes.Length);
postStream.Close(); //获取服务器端的响应
using (HttpWebResponse response = (HttpWebResponse)httpReq.GetResponse())
{
Stream receiveStream = response.GetResponseStream();
StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
string returnValue = readStream.ReadToEnd();
MessageBox.Show(returnValue);
response.Close();
readStream.Close();
}
}
catch (Exception ex)
{
Debug.WriteLine("文件传输异常: "+ ex.Message);
}
finally
{
fileStream.Close();
binaryReader.Close();
}
}

Java 端接收请求

public Map saveCapture(HttpServletRequest request, HttpServletResponse response, Map config) throws Exception {
response.setContentType("text/html;charset=UTF-8");
// 读取请求Body
byte[] body = readBody(request);
// 取得所有Body内容的字符串表示
String textBody = new String(body, "ISO-8859-1");
// 取得上传的文件名称
String fileName = getFileName(textBody);
// 取得文件开始与结束位置
String contentType = request.getContentType();
String boundaryText = contentType.substring(contentType.lastIndexOf("=") + 1, contentType.length());
// 取得实际上传文件的气势与结束位置
int pos = textBody.indexOf("filename=\"");
pos = textBody.indexOf("\n", pos) + 1;
pos = textBody.indexOf("\n", pos) + 1;
pos = textBody.indexOf("\n", pos) + 1;
int boundaryLoc = textBody.indexOf(boundaryText, pos) - 4;
int begin = ((textBody.substring(0, pos)).getBytes("ISO-8859-1")).length;
int end = ((textBody.substring(0, boundaryLoc)).getBytes("ISO-8859-1")).length;
//保存到本地
writeToDir(fileName,body,begin,end);
response.getWriter().println("Success!");
return config;
}
private byte[] readBody(HttpServletRequest request) throws IOException {
// 获取请求文本字节长度
int formDataLength = request.getContentLength();
// 取得ServletInputStream输入流对象
DataInputStream dataStream = new DataInputStream(request.getInputStream());
byte body[] = new byte[formDataLength];
int totalBytes = 0;
while (totalBytes < formDataLength) {
int bytes = dataStream.read(body, totalBytes, formDataLength);
totalBytes += bytes;
}
return body;
}
private String getFileName(String requestBody) {
String fileName = requestBody.substring(requestBody.indexOf("filename=\"") + 10);
fileName = fileName.substring(0, fileName.indexOf("\n"));
fileName = fileName.substring(fileName.indexOf("\n") + 1, fileName.indexOf("\""));
return fileName;
}
private void writeToDir(String fileName, byte[] body, int begin, int end) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("d:/" + fileName);
fileOutputStream.write(body, begin, (end - begin));
fileOutputStream.flush();
fileOutputStream.close();
}

在用request.getParameter()取值的时候,要注意传过来的数据的MIME类型。
GET 方式提交的话,表单项都保存Header中,格式是http://localhost:8080/form?key1=value1&key2=value2 这样的字符串。server端通过request.getParameter("key1")是可以取到值的。
POST 方式,如果为 enctype application/x-www-form-urlencoded,表单数据都保存在HTTP的数据主体,格式类似于下面这样:用request.getParameter()是可以取到数据的。
但是如果enctype 为 multipart/form-data,就和上面的方式一样,表单数据保存在HTTP的数据主体,各个数据项之间用boundary隔开。用request.getParameter()是取不到数据的,这时需要通过request.getInputStream来操作流取数据,需要自己对取到的流进行解析,才能得到表单项以及上传的文件内容等信息。
这种需求属于比较共通的功能,所以有很多开源的组件可以直接利用。比 如:apache的fileupload 组件,smartupload等。通过这些开源的upload组件提供的API,就可以直接从request中取 得指定的表单项了。
在返回值时,只能返回字节流或者字符流,不能同时获取response.getWriter()、response.getOutputStream()。
参考:
《HTTP 权威指南》
http://www.cnblogs.com/txw1958/archive/2013/01/11/csharp-HttpWebRequest-HttpWebResponse.html
http://my.oschina.net/Barudisshu/blog/150026?fromerr=aaqkzmRK
在C#客户端用HTTP上传文件到Java服务器的更多相关文章
- C# 上传文件至远程服务器
C# 上传文件至远程服务器(适用于桌面程序及web程序) 2009-12-30 19:21:28| 分类: C#|举报|字号 订阅 最近几天在玩桌面程序,在这里跟大家共享下如何将本地文件上传 ...
- ASP.NET上传文件到远程服务器(HttpWebRequest)
/// <summary> /// 文件上传至远程服务器 /// </summary> /// <param name="url">远程服务地址 ...
- asp.net 服务器 上传文件到 FTP服务器
private string ftpServerIP = "服务器ip";//服务器ip private string ftpUserID = "ftp的用户名" ...
- java模拟表单上传文件,java通过模拟post方式提交表单实现图片上传功能实例
java模拟表单上传文件,java通过模拟post方式提交表单实现图片上传功能实例HttpClient 测试类,提供get post方法实例 package com.zdz.httpclient; i ...
- .Net 上传文件到ftp服务器和下载文件
突然发现又很久没有写博客了,想起哎呦,还是写一篇博客记录一下吧,虽然自己还是那个渣渣猿. 最近在做上传文件的功能,上传到ftp文件服务器有利于管理上传文件. 前面的博客有写到layui如何上传文件,然 ...
- java 上传文件到 ftp 服务器
1. java 上传文件到 ftp 服务器 package com.taotao.common.utils; import java.io.File; import java.io.FileInpu ...
- PHP 上传文件到其他服务器
PHP 上传文件到其他服务器 标签(空格分隔): 安装Guzzle类库 **guzzle** 是发送网络请求的类库 composer安装:**composer require guzzlehttp/g ...
- SpringBoot上传文件到本服务器 目录与jar包同级问题
目录 前言 原因 实现 不要忘记 最后的封装 Follow up 前言 看标题好像很简单的样子,但是针对使用jar包发布SpringBoot项目就不一样了.当你使用tomcat发布项目的时候,上传 ...
- SpringBoot 上传文件到linux服务器 异常java.io.FileNotFoundException: /tmp/tomcat.50898……解决方案
SpringBoot 上传文件到linux服务器报错java.io.FileNotFoundException: /tmp/tomcat.50898-- 报错原因: 解决方法 java.io.IOEx ...
随机推荐
- vue 表格导出excel
首先要install两个依赖, 1 npm install -S file-saver xlsx 2 npm install -D script-loader 在项目src目录下新建一个文件夹ven ...
- vector 函数都有哪些??
vector 头文件: #include<vector> 因为vector在c++标准模板库,所以还需要加上 : #include<vector> #include<io ...
- 用MyEclipse JPA创建项目(四)
MyEclipse 3.15 Style——在线购买低至75折!火爆开抢>> [MyEclipse最新版下载] 本教程介绍了MyEclipse中的一些基于PA的功能. 阅读本教程时,了解J ...
- HTML项目总结
一些存在的可以改进的地方: 达到一个效果不必要墨守成规,比如:
- 201621123010《Java程序设计》第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 为你的系统增加网络功能(购物车.图书馆管理.斗地主等)-分组完成 为了让你的系统可以被多个用户通过网 ...
- 基于PHP+MYSQL的WEB聊天应用雏形开发实例教程
更多技术资源:胡旭个人博客 前天,简单利用PHP做了一个简单的WEB聊天应用.没有发到网上,所以就不提供demo了.不过,发布一下源代码(见文尾). 项目说明: 基于PHP+MYSQL的WEB聊天应用 ...
- IDC:2014年的十大 IT 趋势
IDC:2014年的十大 IT 趋势 市场研究公司 IDC 近日发布报告,对 2014 年的十大科技行业发展趋势作出了预测.IDC 称,2014 年将是科技业"鏖战正酣"的一年,整 ...
- Object -c基础知识(5)--release 之后 retainCount为何为1
在XCode中加入如下代码: UILabel *label=[UILabel alloc]; [label setText:@"TestLabel"]; NSLog(@" ...
- PHP连接MYSQL 报错"No such file or directory"
首先确定是mysql_connect()和mysql_pconnect()的问题,故障现象就是函数返回空,而mysql_error()返回“No such file or directory”. 写个 ...
- magento导航栏中如何加入home主页
magento在导航栏中加入home主页是很简单的,几个步骤即可在magento导航栏中加入home主页! 下面简单介绍下如何在magento导航栏中加入home主页: 首先我们打开对应应用的模板文件 ...