Http网络框架的构建
来源:互联网 发布:耐药监测数据处理软件 编辑:程序博客网 时间:2024/05/01 01:29
参考:https://github.com/hehonghui/simple_net_framework
一、了解整个框架的布局
结构图:
关系分析:
①、Request类
作用:设置请求数据(url,header,body等),解析返回的数据,并设置数据完成接口
②、RequestQueen类
作用:启动与关闭执行线程、并能够添加Request到队列中。
③、NetWorkThread类
作用:设置异步环境,在此执行网络数据交互,并分发完成后的数据。
④、HttpStack类
作用:网络交互的真正执行类。
⑤、ResponseDelivery
作用:分发完成后的数据。
⑥、Response类
作用:封装服务器发过来的数据
⑦、HttpEntity类
Response分为header、与entity两个部分,HttpEntity掌管Response的实体。
二、构建Request类
1、Request类需要完成的任务
①、需要有URL,请求的编码,请求的ContentType,请求的类型(GET,PUT)。(就是固定的Http请求头的格式)
不了解请求头的参照 :网络——深入了解Http
②、能够添加额外的请求头
③、如果非GET类型,要能够附带请求数据(Params)
④、能够设置请求在队列中的优先级(重写equal(),hashcode(),compareTo)
⑤、能够解析特定数据并分发。
2、开始制作
第一步:获取URL路径
public abstract class Request<T>{ //第一步:获取url private String mUrl; public Request(String url){ mUrl = url; } } //设置set、get方法,以后就不写了 public void setUrl(String url){ mUrl = url; } public String getUrl(){ return mUrl; }}
第二步:根据请求头的格式,设置请求头和需要上传的数据
①、请求类型 ②、用容器存储请求头 ③、如果存在POST、PUT这样的请求,需要设置请求的Body与body的Content-Type及编码
//创建泛型,来设置请求类型,防止用户输入错误 public enum HttpMethod{ GET("GET"), POST("POST"), PUT("PUT"), DELETE("DELETE"); private String brief; private HttpMethod(String brief){ this.brief = brief; } @Override public String toString() { return brief; }}//设置默认类型为GETprivate HttpMethod mMethod = HttpMethod.GET;//用容器存储请求头与需要提交的body,add()与get()就不写了。private final Map<String,String> mHeaders = new HashMap<>();private final Map<String,String> mParams = new HashMap<>();/**设置编码*///首先默认的编码与Content-Typeprivate static final String DEFAULT_PARAMS_ENCODING = "UTF-8";private static final String HEADER_CONTENT_TYPE = "Content-Type";//设置编码,set()与get()不显示 private String mParamsEncoding = DEFAULT_PARAMS_ENCODING;//返回Content-Type的内容 public String getBodyContentType(){ return "application/x-www-form-unlencoded;charset="+getParamsEncoding(); }补:处理上传的数据。上传数据不能以键值对上传,我们需要一种转换格式,为xx=kk&mm=nn这样的格式
public String getBodyParams(){ final Set<Map.Entry<String,String>> paramsEntry = getParams().entrySet(); final StringBuilder sb = new StringBuilder(); //设置上传的格式 for(Map.Entry<String,String> entry : paramsEntry){ sb.append(entry.getKey()+"="+entry.getValue()+"&"); } //由于最后会多一个&需要进行处理 String data = sb.toString(); if (data == ""){ return data; } else { //向前移动一位截取 return data.substring(0,data.length()-1); } }第三步:设置Request类在队列中的优先级
由:优先级和序列号控制
<pre name="code" class="java">public abstract class Request<T> implements Comparable<Request<T>>{<span style="white-space:pre"></span>//设置优先级枚举<span style="white-space:pre"></span>public enum Priority{ <span style="white-space:pre"></span> LOW, <span style="white-space:pre"></span> NORMAL, <span style="white-space:pre"></span> HIGHT, <span style="white-space:pre"></span> IMMEDIATE;<span style="white-space:pre"></span>}<span style="white-space:pre"></span>//设置优先级 并设置set()与get()<span style="white-space:pre"></span>private Priority mPriority = Priority.NORMAL;<span style="white-space:pre"></span>private int mSerialNum = 0;<span style="white-space:pre"></span>//通过继承comparable进行比较<span style="white-space:pre"></span>@Override<span style="white-space:pre"></span>public int compareTo(Request<T> another) { Priority myPriority = getPriority(); <span style="white-space:pre"></span> Priority anotherPriority = another.getPriority(); //两组序列相减,比大小 <span style="white-space:pre"></span> return myPriority == anotherPriority ? getSerialNum() - another.getSerialNum() : myPriority.ordinal() - anotherPriority.ordinal(); }}
第四步:处理接收后的Response并分发。
//设置抽象方法,让子类实现解析过程public abstract T parseResponse(Response response);//设置当完成之后的回调监听public interface RequestListener<V>{ //返回获取并解析后的数据,及状态码 public void complete(V response,int code);}//设置分发的方法 public void deliverResponse(Response response){ T result = parseResponse(response); int code = response.getStatusCode(); if (mReqListener != null){ mReqListener.complete(result,code); } }注:现在还未写Response方法,所以就先将逻辑写上。
第五步:继承该抽象类,设置完整的解析类
public class JsonRequest extends Request<JSONObject> { public JsonRequest(String url, RequestListener<JSONObject> listener) { super(url, listener); } @Override public JSONObject parseResponse(Response response) { //获取response的数据并解析 JSONObject jsonObject = new JSONObject(); byte[] bytes = response.getRowData(); try { String data = bytes.toString(); jsonObject = new JSONObject(data); } catch (JSONException e) { e.printStackTrace(); } return jsonObject; }}
三、构建Response类
需求:
1、存储头部,和内容
2、返回状态码,和状态信息
3、返回头部和内容数据
为了方便,继承BasicHttpResponse类。
该类属于org,apache.http包中,由于API23删除了该包,需要自己从AndroidSDK文件夹中获取Jar包。
public class Response extends BasicHttpResponse { /** * 构造方法为状态栏信息 * ProtocolVersion表示:HTTP/1.1 * code表示:服务器返回的状态码 * reason表示:伴随状态码的信息 * */ public Response(ProtocolVersion ver, int code, String reason) { super(ver, code, reason); } //父类自带addHeader与getHeader @Override public void addHeader(Header header) { super.addHeader(header); } //父类自带addEntity与getEntity @Override public void setEntity(HttpEntity entity) { super.setEntity(entity); } public int getStatusCode(){ return getStatusLine().getStatusCode(); } public String getStatusMessage(){ return getStatusLine().getReasonPhrase(); } //将entity设置为byte返回 public byte[] getRowData(){ try { byte [] data = EntityUtils.toByteArray(getEntity()); return data; } catch (IOException e) { e.printStackTrace(); } return new byte[1024]; }}
四、构建HttpStack类
既然两个基础类都做好了,那么就开始数据传输。
由于在API 9 的时候,使用的是HttpClient,9以上的时候使用的是HttpUrlConnection,为方便扩展,将HttpStack设置为接口。
public interface HttpStack { public Response performRequest();}
在创建HttpUrlConnStack前,我们还需要创建一个类,专门用来设置Connection的Config
叫做:HttpUrlConnConfig
public class HttpUrlConnConfig { public static int connTimeOut = 10000; public static int soTimeOut = 10000;}
需求:
1、创建UrlConnection,并创建配置器
2、将Request参数,完整设置到UrlConnection中
3、获取流的数据,并转化为Response
*/public class HttpUrlConnStack implements HttpStack { private static final String TAG = "HttpUrlConnStack"; @Override public Response performRequest(Request<?> request) { HttpURLConnection conn = null; try { conn = createUrlConnection(request); setRequestHeaders(conn,request); setRequestParams(conn,request); Response response = fetchResponse(conn); return response; } catch (IOException e) { e.printStackTrace(); } return null; } /** * 创建连接器 * @param request * @return * @throws IOException */ private HttpURLConnection createUrlConnection(Request<?> request) throws IOException{ URL url = new URL(request.getUrl()); URLConnection conn = url.openConnection(); conn.setConnectTimeout(HttpUrlConnConfig.connTimeOut); conn.setReadTimeout(HttpUrlConnConfig.soTimeOut); conn.setDoInput(true); return (HttpURLConnection)conn; } private void setRequestHeaders(HttpURLConnection conn,Request<?> request){ Set<Map.Entry<String,String>> headerEntry = request.getHeaders().entrySet(); for(Map.Entry<String,String> entry : headerEntry){ conn.setRequestProperty(entry.getKey(),entry.getValue()); } } private void setRequestParams(HttpURLConnection conn,Request<?> request) throws IOException{ conn.setRequestMethod(request.getHttpMethod().toString()); String params = request.getBodyParams(); if (params != ""){ conn.setDoOutput(true); conn.setRequestProperty(Request.HEADER_CONTENT_TYPE,request.getBodyContentType()); OutputStream os = conn.getOutputStream(); os.write(params.getBytes()); os.close(); } } private Response fetchResponse(HttpURLConnection conn){ ProtocolVersion version = new ProtocolVersion("HTTP",1,1); try { int code = conn.getResponseCode(); String msg = conn.getResponseMessage(); Response response = new Response(version,code,msg); //设置Response的头部 addResponseHeader(conn,response); //设置Response的内容 response.setEntity(entityFromConn(conn)); return response; } catch (IOException e) { e.printStackTrace(); Log.e(TAG,"捕获Response错误"); } return null; } private void addResponseHeader(HttpURLConnection conn,Response response){ Set<Map.Entry<String,List<String>>> headers = conn.getHeaderFields().entrySet(); for(Map.Entry<String,List<String>> entry : headers){ String key = entry.getKey(); for(String value : entry.getValue()){ Header header = new BasicHeader(key,value); response.addHeader(header); } } } private HttpEntity entityFromConn(HttpURLConnection conn) throws IOException{ BasicHttpEntity entity = new BasicHttpEntity(); InputStream is = conn.getInputStream(); entity.setContent(is); entity.setContentLength(conn.getContentLength()); entity.setContentType(conn.getContentType()); entity.setContentEncoding(conn.getContentEncoding()); is.close(); return entity; }}
五、构建NetWorkThread
需求:
1、从队列中获取Request,然后将其传递给HttpStack执行
2.、获取Response然后分发数据
public class NetWorkThread extends Thread { //所有线程共享,所以设置为static private static BlockingQueue<Request<?>> mQueue; private static HttpStack mHttpStack; //创建分发器 private static RequestDelivery mDelivery = new RequestDelivery(); private boolean isStop = false; public NetWorkThread (BlockingQueue<Request<?>> queue, HttpStack httpStack){ mQueue = queue; mHttpStack = httpStack; } @Override public void run() { super.run(); try { while (!isStop){ Request<?> request = mQueue.take(); Response response = mHttpStack.performRequest(request); //创建分发器 mDelivery.deliverResponse(request,response); } } catch (InterruptedException e) { e.printStackTrace(); } } public void quit(){ isStop = true; interrupt(); }}分发器:
public class RequestDelivery implements Executor{ //获取主线程的Handler private Handler mHander = new Handler(Looper.getMainLooper()); public void deliverResponse(final Request<?> request, final Response response){ Runnable runnable = new Runnable() { @Override public void run() { request.deliverResponse(response); } }; execute(runnable); } @Override public void execute(Runnable command) { mHander.post(command); }}
六、构建RequestQueue队列
需求:
1、创建、终止线程
2、添加Requst到队列中
①、初始化RequestQueue
public class RequestQueue { //创建队列 private static final PriorityBlockingQueue<Request<?>> QUEUE = new PriorityBlockingQueue<>(); private static RequestQueue sRequestQueue; //设置使用哪种方式处理网络传输,(HttpStack未实现) private static HttpStack sStack; //统一使用一条队列存储数据,所以采用单例模式 private RequestQueue(HttpStack httpStack){ sStack = httpStack; } public RequestQueue getInstance(HttpStack httpStack){ if (sRequestQueue == null){ sRequestQueue = new RequestQueue(httpStack); } return sRequestQueue; } public void setHttpStack(HttpStack httpStack){ sStack = httpStack; }}②、创建与停止线程
private static final int DEFAULT_THREAD_SIZE = Runtime.getRuntime().availableProcessors()+1; //暂未创建,知道是线程就可以了(<span style="font-family: Arial, Helvetica, sans-serif;">NetWorkThread 暂未创建</span><span style="font-family: Arial, Helvetica, sans-serif;">)</span> private static final NetWorkThread[] mThreads = new NetWorkThread[DEFAULT_THREAD_SIZE]; //创建处理线程 public void startExecutor(){ for(int i=0 ; i<mThreads.length; ++i){ mThreads[i] = new NetWorkThread(); } } //终止线程 public void stop(){ for(int i=0; i<mThreads.length; ++i){ NetWorkThread thread = mThreads[i]; if (thread != null){ thread.quit(); mThreads[i] = null; } } } //开启线程 public void start(){ stop(); startExecutor(); }③、添加Request数据
private static final AtomicInteger mSerialNum = new AtomicInteger(); public void addRequest(Request<?> request){ //设置序列号 request.setSerialNum(mSerialNum.getAndIncrement()); QUEUE.add(request); }
使用:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //创建队列 RequestQueue queue = RequestQueue.getInstance(new HttpUrlConnStack()); //启动队列 queue.start(); //创建Request请求 JsonRequest request = new JsonRequest("https://www.baidu.com/", new Request.RequestListener<JSONObject>() { @Override public void complete(JSONObject response, int code) { Log.d("MainActivity","哈哈"); } }); queue.addRequest(request); }}
七、如何仿表单混合上传文件和参数
①、创建MultipartEntity类
首先看一下,普通表单请求的代码:
POST login.php HTTP1.1
Accept-Encoding:gzip
Content-Type:multipart/form-data; boundary=ABCD //Content-Type:表示这是一个表单 boundary:表示分隔符
Content-Length:22350
Host:http://write.blog.csdn.net
Connection:Keep-Alive
--ABCD //通过--加上boundary的值:表示这是表单的一个数据
Content-Disposition:form-data;name="username" //key值
Content-Type:text/plain; charset=UTF-8 //key的类型
Content-Transfer-Encoding:8bit //key的大小
//空白行,必须加
chen //value的值
--ABCD
Content-Disposition:form-data;name="images"
filename="storage/emulated/0/Camera/picture/tempeter.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding:binary
(这里是图片的二进制数据)
--ABCD-- //表示终止
那么先从头部开始我们需要些哪些内容
Content-Type:multipart/form-data; boundary=ABCD
Content-Length:22350
这两部分是需要我们书写的。
表单部分我们需要书写哪些内容
普通参数的上传格式
--ABCD
Content-Disposition:form-data;name="username"
Content-Type:text/plain; charset=UTF-8
Content-Transfer-Encoding:8bit
(这里是数据)
文件的上传格式:
--ABCD
Content-Disposition:form-data;name="images";filename="storage/emulated/0/Camera/picture/tempeter.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding:binary
所以说,我们只要仿照这份数据本文,传给服务器就可以。
原理:
1、首先设置请求的头
2、将这份表单中的相同部分和不同部分设置成参数
3、将生成的数据存储在ByteArrayoutputStream中
public class MultipartEntity implements HttpEntity { /** * 第一步: * 1、将可以写的参数都写出来 * 2、创建随机的Boundary * 3、创建存储数据的容器ByteArrayStream */ //提供默认的参数编码 public static final String CHARTSET_UTF = "UTF-8"; //可获取的字符串,用于生成随机的Boundary private static final char[] ALL_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); //头部的参数名 private static final String HEADER_CONTENT_TYPE = "Content-Type:"; private static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition:"; private static final String HEADER_TRANSFER_ENCODING = "Content-Transfer-Encoding:"; //用来换行用的 private static final String CHANGE_ROW = "\r\n"; //Type的参数值 private static final String TYPE_MULTIPART = "multipart/form-data"; private static final String TYPE_TEXT_PLAIN = "text/plain"; private static final String TYPE_OCTET_STREAM = "application/octet-stream"; //Disposition的参数值 private static final String DISPOSITION_FORM = "form-data"; //Transfer_Encoding的参数指 private static final String ENCODING_TEXT = "8bit"; private static final String ENCODING_FILE = "binary"; //一直用的键值对 private static final String DISPOSITION = HEADER_CONTENT_DISPOSITION+DISPOSITION_FORM+";"; private final ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream(); private final String mBoundary; public MultipartEntity() { mBoundary = createBoundary(); } private String createBoundary(){ StringBuilder sb = new StringBuilder(); Random random = new Random(); for(int i=0; i<30; ++i){ sb.append(ALL_CHARS[random.nextInt(ALL_CHARS.length)]); } return sb.toString(); } /** * 第三步:创建添加String,File键值对的方法 * 然后写入到ByteOutputStream中 */ public void addStringPart(String name,String value,String charset){ writeToStream(name,null,value,TYPE_TEXT_PLAIN,charset,ENCODING_TEXT); } public void addFilePart(String name,String fileName,String filePath){ writeToStream(name,fileName,filePath,TYPE_OCTET_STREAM,null,ENCODING_FILE); } private void writeToStream(String name,String fileName,String value, String type,String charset,String transferEncoding){ try { //表单的请求头 mOutputStream.write(getFirstBoundary()); mOutputStream.write(getContentDisposition(name,fileName)); mOutputStream.write(getContentType(type,charset)); mOutputStream.write(getTransferEncoding(transferEncoding)); //换行 mOutputStream.write(CHANGE_ROW.getBytes()); //填写数据 if (fileName == null){ mOutputStream.write((value+CHANGE_ROW).getBytes()); } else { //未考虑到File地址不正确的情况 File file = new File(value); FileInputStream fis = new FileInputStream(file); byte [] bytes = new byte[4096]; int len = 0; while ((len = fis.read(bytes)) != -1){ mOutputStream.write(bytes,0,bytes.length); } mOutputStream.flush(); fis.close(); } } catch (IOException e) { e.printStackTrace(); } } private byte[] getFirstBoundary(){ return ("--"+mBoundary+CHANGE_ROW).getBytes(); } private byte[] getContentDisposition(String name,String fileName){ //这是Text文本 if (fileName == null){ //输出就是 Content-Disposition:form-data;name="xxx" return (DISPOSITION+"name=\""+name+"\""+CHANGE_ROW).getBytes(); } else { //输出就是Content-Disposition:form-data;name="images";filename="xxx" return (DISPOSITION+"name=\""+name+"\";fileName=\""+fileName+"\""+CHANGE_ROW).getBytes(); } } private byte[] getContentType(String type,String charset){ //这是text文本 if (charset != null){ return (HEADER_CONTENT_TYPE+type+";charset="+charset+CHANGE_ROW).getBytes(); } else { return (HEADER_CONTENT_TYPE+type+CHANGE_ROW).getBytes(); } } private byte[] getTransferEncoding(String transferEncoding){ return (HEADER_TRANSFER_ENCODING+transferEncoding+CHANGE_ROW).getBytes(); } @Override public boolean isRepeatable() { return false; } @Override public boolean isChunked() { return false; } /** * 第二步:设置请求头 * */ @Override public long getContentLength() { return mOutputStream.toByteArray().length; } @Override public Header getContentType() { return new BasicHeader(HEADER_CONTENT_TYPE, TYPE_MULTIPART +";boundary="+mBoundary); } @Override public Header getContentEncoding() { return null; } @Override public InputStream getContent() throws IOException, IllegalStateException { return null; } @Override public void writeTo(OutputStream outputStream) throws IOException { //设置结束符 mOutputStream.write(("--"+mBoundary+"--").getBytes()); byte [] bytes = mOutputStream.toByteArray(); outputStream.write(bytes); mOutputStream.close(); } @Override public boolean isStreaming() { return false; } @Override public void consumeContent() throws IOException { }}
--ABCD //通过--加上boundary的值:表示这是表单的一个数据
Content-Disposition:form-data;name="username" //key值
Content-Type:text/plain; charset=UTF-8 //key的类型
Content-Transfer-Encoding:8bit
0 0
- Http网络框架的构建
- 网络框架构建
- Android 网络框架_网络框架的核心Http协议
- Http网络请求库框架的使用
- android-async-http网络框架的使用
- HTTP(S)网络框架的设计
- 一步一步构建你的iOS网络层 - HTTP篇
- Android基于http的网络请求async-http框架
- http网络请求框架
- Android网络框架xUtils的Http网络数据请求操作
- 采用beego框架构建 http服务器
- 自己封装的android客户端http网络框架
- Android网络框架之Http请求的分发与执行
- iOS HTTP网络请求,没有使用框架的request
- android 设计一个简易的Http网络请求框架
- Android基于http封装的网络请求框架
- Java HTTP 网络请求库框架的使用
- 【java】快速访问http网络请求框架QuickHttp的使用
- 很简单的JSP问题,<base href="<%=basePath%>">这句话什么意思?
- SUZUKI最新专利图流出,SX-R排量大猜想
- 中断和异常的建立
- caffe中HingeLossLayer层原理以及源码分析
- 堆排序
- Http网络框架的构建
- OkHttp学习(3)-->>同步、异步之上传文件至服务器(重写RequestBody方法,实现上传进度接口回调)
- Atitti.java exp ast java表达式语法ast构造器
- C/C++ 头文件作用
- 让div变为圆角
- json学习笔记
- UI设计的狂暴之路(PS篇)---设计知识学习路线概要以及PS在日常生活中的关键作用
- 常用的jquery方法,学会你也变大神
- 在unity环境下用ngui实现转盘