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;}

创建其中的子类HttpUrlConnStack
需求:
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