android端实现http服务器,具备文件上传等功能,纯JAVA实现,无依赖包
来源:互联网 发布:led屏编辑软件 编辑:程序博客网 时间:2024/06/03 22:38
最近需要在我们的安卓设备上实现通过网页访问设备,进行相关配置、上传数据等操作,因此就需要在安卓端实现一个http服务器。(其实代码也可以用于PC端,只不过PC端已经有太多成熟的框架了,JDK7/8之后貌似就内置了一个轻量的HTTP服务器)。
采用java socket实现的http服务器网上有较多的例子,但是例子大部分都比较简单,不具备文件上传的功能,于是结合网上的列子动手写了个具备文件上传、请求资源文件、处理请求的简单的http服务器,需要的朋友可以参考下:
- 创建socket 监听,客户端浏览器在打开地址后,就会向服务器建立一个tcp连接(http协议是基于TCP协议封装的)
- 服务器获取客户端的输入流并解析头部,也就是说需要解析浏览器封装的HTTP协议信息
- 根据解析的头部信息判断客户端请求的资源类型,并进行相应的处理
处理http请求的部分,这里不再详述,大家也可直接参考代码,重点说下文件上传部分:
1、根据Content-Type来判断当前是否为文件上传请求,即:
this.contentType.startsWith(“multipart/form-data”);表示当前为上传文件请求
2、接下来就是根据RFC协议去解析请求体中的上传文件数据了, RFC协议中规定的http上传文件文件格式如下:
其中的——————————–1878979834就是“边界值”,是文件数据的开始、结束标志。
一开始也就是在这里遇到的难处,不知道是不是有的人也跟我一开始的做法类似,使用bufferedReader去按行解析,最后却发现提取出来的数据与实际的文件不一致、不完整。并不是编码格式的原因。原因应该是bufferedReader默认会提前缓存了一部分数据,导致最终按行读取获取到的数据不完整(距离问题解决过去了很久,具体原因是不是这个原因记得不是太清)。
总之最后发现在如果文件上传的话就不能采用bufferdReader去按行读取。既然这样那就得自己去按字节解析,也就是说自己去实现readLine,可以看到,我实现了两个readline方法,第一个读取一行后返回改行的字符串,用于解析http请求的头部。第二个将一行的数据(包括换行符)读取到byte[]用于提取上传的文件。以及一个快速比较byte数组的方法,用于快速判断当前是否读取到了文件的边界,代码如下:
/** * 读取一行 * @return */ private String readLine() { try { byte[] buffer = new byte[1024];//默认缓存1024 int b = 0, pos = 0; while ((b = mStream.read()) >= 0) { buffer[pos] = (byte) b; if (pos > 0 && buffer[pos] == 0x0A && buffer[pos - 1] == 0x0D) {//读取到换行符0d 0a或13 10 表示换行 return new String(buffer, 0, pos - 1); } pos++;//当前坐标+1 if (pos == buffer.length) {//缓冲区已满则扩充缓存区 byte[] old = buffer; buffer = new byte[old.length + 1024];//每次扩充1024 System.arraycopy(old, 0, buffer, 0, old.length); } } return new String(buffer, 0, pos); } catch (Exception ex) { return null; } } /** * 读取一行byte,返回读取的长度 * @param buffer * @return */ private int readLine(byte[] buffer) { int b = 0, pos = 0; try { while (pos<buffer.length&&(b = mStream.read()) !=-1) { buffer[pos] = (byte) b; if (pos > 0 && buffer[pos] == 0x0A && buffer[pos - 1] == 0x0D) {//读取到换行符0d 0a或13 10 表示换行 return pos+1;//返回读取的长度 } pos++;//当前坐标+1 } } catch (Exception ex) { } return pos; } /** * 比较byte数组 * * @param src * @param des * @return */ private boolean startsWith(byte[] src, byte[] des) { if (src.length < des.length) { return false; } for (int i = 0; i < des.length; i++) { if (src[i] != des[i]) { return false; } } return true; }
其实关键就是需自己去实现readline方法,下面是获取客户端输入流后,解析输入流的过程代码:
public MyRequest(InputStream in) { mStream = new BufferedInputStream(in); try { //读取第一行, 请求地址 String line = this.readLine(); if (line == null) { return; } // 获得请求的资源的地址 String resource = line.substring(line.indexOf("/"), line.lastIndexOf("/") - 5); this.requestUrl = URLDecoder.decode(resource, "UTF-8");// 反编码 URL地址 this.method = new StringTokenizer(line).nextElement().toString();// 获取请求方法, GET或者POST // 读取所有浏览器发送过来的请求参数头部信息 int contentLen = 0;// 如果为POST方法,则会有消息体长度 while ((line = this.readLine()) != null) { Log.d(TAG, line); if (line.startsWith("Content-Length")) { contentLen = Integer.parseInt(line.split(":")[1].trim()); } if (line.startsWith("Content-Type")) { this.contentType = line.split(":")[1].trim(); } if (line.equals("")) {// 空行表示头结束 break; } } if ("POST".equalsIgnoreCase(this.method) && contentLen > 0) {// 显示 POST表单提交的内容, 这个内容位于请求的主体部分 try { if (this.contentType != null && this.contentType.startsWith("multipart/form-data")) {// 上传文件请求 this.filePath=this.extractRFCFile(); } else { byte[] buffer = new byte[contentLen]; mStream.read(buffer, 0, buffer.length);// 不能调用readline,否则会导致阻塞 String postTextBody = new String(buffer); this.params = parseQueryParam(postTextBody); } } catch (Exception ex) { logger.error("读取POST数据出错", ex); } } int queryIndex = this.requestUrl.indexOf("?"); if (queryIndex > 0) { this.url=this.requestUrl.substring(0,queryIndex); if (this.params == null) { this.params = new HashMap<String, String>(); } this.params.putAll(parseQueryParam(this.requestUrl.substring(queryIndex + 1))); }else{ this.url=this.requestUrl; } } catch (Exception ex) { logger.error("解析http请求为request出错", ex); } }
下面是提取上传文件并写到本地临时文件的代码:
/** * 提取http上传的文件 * @return */ private String extractRFCFile() { byte[] boundary = this.readLine().getBytes();// 根据RFC协议第一行是边界 Log.d(TAG, "上传数据,边界为:" + new String(boundary)); String line; while ((line = this.readLine()) != null) { Log.d(TAG, line); if (line.startsWith("Content-Disposition")) { //获取上传的文件名 } if (line.equals("")) {// 空行表示头结束 break; } } String savePath=Confing.CLIENT_BASEPATH+"upload.tmp"; try { FileOutputStream fos = new FileOutputStream(savePath); byte[] buffer = new byte[1024]; int count = 0; while ((count = this.readLine(buffer)) > 0) { if (this.startsWith(buffer, boundary)) { Log.d(TAG, "文件读取结束"); break; } fos.write(buffer, 0, count);//会导致多一个换行符 } fos.flush(); fos.close(); } catch (Exception ex) { logger.error("提取HTTP上传的文件出错", ex); return null; } return savePath; }
下面是接受一个客户端socket接入后的处理部分代码:
static class HttpWorkHandle extends Thread { private Socket client; public HttpWorkHandle(Socket socket) { this.client = socket; } public void run() { try { if (client != null) { logger.info("连接到服务器的用户:" + client); InputStream ins = null; OutputStream out = null; try { ins = client.getInputStream(); out = client.getOutputStream(); MyRequest request = new MyRequest(ins); if (request.method == null) { return; } MyResponse response = new MyResponse(out); //根据contentType判断是不是业务类请求 if (request.contentType.startsWith("application/x-www-form-urlencoded") || request.contentType.startsWith("application/json") || request.contentType.startsWith("multipart/form-data") || request.url.endsWith(".do")) { PrintWriter pw = new PrintWriter(out, true); String responseText = new WebDispatcher(mContext).doRespond(request, response); pw.println("HTTP/1.0 200 OK");// 返回应答消息,并结束应答 pw.println("Content-Type:text/plain;charset=UTF-8"); pw.println();// 根据 HTTP 协议, 空行将结束头信息 if (responseText != null) { pw.print(responseText); } pw.close(); } else {// 请求资源文件 try { String resource; if (request.requestUrl.equals("/") || request.requestUrl.endsWith(SERVNAME)) { resource = "web/index.html"; } else { resource = "web" + request.requestUrl.substring(request.requestUrl.indexOf(SERVNAME) + SERVNAME.length()); } InputStream stream = null; try { stream = mContext.getAssets().open(resource); } catch (Exception ex) { Log.d("HTTPServer", "资源文件不存在:" + resource); } if (stream != null) { byte[] buffer = new byte[1024 * 4]; int len; while ((len = stream.read(buffer)) != -1) { out.write(buffer, 0, len); } out.flush(); stream.close(); } else { out.write(this.errorMessage().getBytes());// 返回失败信息 } } catch (Exception ex) { logger.error("客户端请求资源文件出错", ex); } } } catch (Exception ex) { logger.error("HTTP服务器错误", ex); } finally { if (ins != null) { ins.close(); } if (out != null) { out.close(); } client.close(); } } } catch (Exception ex) { logger.error("处理客户端请求出错", ex); } }
以下是别人总结的Http协议相关的东西,结合这个基本就可以理解在解析http头部时的一些步骤了:
** HTTP请求包括的内容
客户端连上服务器后,向服务器请求某个web资源,称之为客户端向服务器发送了一个HTTP请求。
一个完整的HTTP请求包括如下内容:一个请求行、若干消息头、以及实体内容,范例:
HTTP请求的细节——请求行
请求行中的GET称之为请求方式,请求方式有:POST、GET、HEAD、OPTIONS、DELETE、TRACE、PUT,常用的有: GET、 POST
用户如果没有设置,默认情况下浏览器向服务器发送的都是get请求,例如在浏览器直接输地址访问,点超链接访问等都是get,用户如想把请求方式改为post,可通过更改表单的提交方式实现。
不管POST或GET,都用于向服务器请求某个WEB资源,这两种方式的区别主要表现在数据传递上:如果请求方式为GET方式,则可以在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔,例如:GET /mail/1.html?name=abc&password=xyz HTTP/1.1
GET方式的特点:在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。
如果请求方式为POST方式,则可以在请求的实体内容中向服务器发送数据,Post方式的特点:传送的数据量无限制。
HTTP请求的细节——消息头
HTTP请求中的常用消息头
Accept代表发送端(客户端)希望接受的数据类型
Content-Type代表发送端(客户端|服务器)发送的实体数据的数据类型
Accept-Charset: 浏览器通过这个头告诉服务器,它支持哪种字符集
Accept-Encoding:浏览器通过这个头告诉服务器,支持的压缩格式
Accept-Language:浏览器通过这个头告诉服务器,它的语言环境
Host:浏览器通过这个头告诉服务器,想访问哪台主机
If-Modified-Since: 浏览器通过这个头告诉服务器,缓存数据的时间
Referer:浏览器通过这个头告诉服务器,客户机是哪个页面来的 防盗链
Connection:浏览器通过这个头告诉服务器,请求完后是断开链接还是何持链接
以application开头的媒体格式类型:
• application/xhtml+xml :XHTML格式
• application/xml : XML数据格式
• application/atom+xml :Atom XML聚合格式
• application/json : JSON数据格式
• application/pdf :pdf格式
• application/msword : Word文档格式
• application/octet-stream : 二进制流数据(如常见的文件下载)
• application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
• multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式
HTTP响应包括的内容
一个HTTP响应代表服务器向客户端回送的数据,它包括: 一个状态行、若干消息头、以及实体内容 。
HTTP响应的细节——状态行
状态行格式: HTTP版本号 状态码 原因叙述
举例:HTTP/1.1 200 OK
状态码用于表示服务器对请求的处理结果,它是一个三位的十进制数。响应状态码分为5类,如下所示:
5.3、HTTP响应细节——常用响应头
HTTP响应中的常用响应头(消息头)
Location: 服务器通过这个头,来告诉浏览器跳到哪里
Server:服务器通过这个头,告诉浏览器服务器的型号
Content-Encoding:服务器通过这个头,告诉浏览器,数据的压缩格式
Content-Length: 服务器通过这个头,告诉浏览器回送数据的长度
Content-Language: 服务器通过这个头,告诉浏览器语言环境
Content-Type:服务器通过这个头,告诉浏览器回送数据的类型
Refresh:服务器通过这个头,告诉浏览器定时刷新
Content-Disposition: 服务器通过这个头,告诉浏览器以下载方式打数据
Transfer-Encoding:服务器通过这个头,告诉浏览器数据是以分块方式回送的
Expires: -1 控制浏览器不要缓存
Cache-Control: no-cache
Pragma: no-cache
**
- android端实现http服务器,具备文件上传等功能,纯JAVA实现,无依赖包
- Jsp实现文件上传(无依赖jar包)
- java实现文件上传ftp服务器功能
- [原创]纯java实现http web服务器
- Android之使用Http协议实现文件上传功能
- Android之使用Http协议实现文件上传功能
- Android之使用Http协议实现文件上传功能
- Android之使用Http协议实现文件上传功能
- JAVA实现FTP服务器文件的上传,下载,删除功能
- android实现文件上传功能
- Java实现文件上传功能
- Golang实现http文件上传小功能
- 【java实现web文件无刷新上传】
- 纯Servlet实现文件上传
- Android开发:相册读取、拍照、图片裁剪和图片上传服务器等功能的实现
- Android--使用Http协议实现文件上传
- android通过http协议实现文件上传
- [Android开发]Android之使用Http协议实现文件上传功能
- 【Linux c】socket+thread【preIot project】
- Android沉浸式状态栏SystemBarTint的使用方法
- JSON与XML的优缺点比较
- 内部命令VS外部命令
- UVA 796 Critical Links 求桥 .
- android端实现http服务器,具备文件上传等功能,纯JAVA实现,无依赖包
- PHP定时执行任务 crontab
- Html特殊字符
- HTML基础篇——a标签
- 三极管三种基本放大电路
- 谈谈关于内存对齐与补齐
- 判断一个数是否是完数
- Intel SGX设计目标
- input标签加图标,可点击