Http网络框架的构建

参考: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;
       }
}
//设置默认类型为GET
private HttpMethod mMethod = HttpMethod.GET;

//用容器存储请求头与需要提交的body,add()与get()就不写了。
private final Map<String,String> mHeaders = new HashMap<>();
private final Map<String,String> mParams = new HashMap<>();
/**设置编码*/
//首先默认的编码与Content-Type
private 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.youkuaiyun.com
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     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值