okhttp获取服务器响应码,okhttp源码解析-网络协议的实现-请求流程:请求的发送与响应的接收...

http协议相关

一个http请求的过程

输入url和参数

->DNS解析(访问域名服务器53号端口,根据域名拿到ip,可能会拿到好几个ip)

->根据ip和端口号连接socket(TCP三次握手已封装在socket api内部,对开发者透明)

-> socket连接成功后,往socket输出流中写入http报文

一个响应的接收过程

服务器接受请求并处理后发出响应

客户端从socket输入流中读取http报文

注意: 请求行/状态行和报文头部是字符,而请求体和响应体可以是字符,也可以是二进制流

http请求报文的格式

591afe572621

Paste_Image.png

注: 下图第一行名字标错了,是叫请求行.状态行是响应报文中的叫法.

591afe572621

Paste_Image.png

特殊一点的: 文件上传的http报文格式:

注:下图请求参数为:

键值对:

"uploadFile555","1474363536041.jpg"

"api_secret777","898767hjk"

文件:

"uploadFile","/storage/emulated/0/qxinli.apk"

4c653270f446fa45592c.jpg

upload

http响应报文的格式

591afe572621

Paste_Image.png

591afe572621

Paste_Image.png

常见的json response:

591afe572621

Paste_Image.png

okhttp中代码执行流程

直接看GetExample中的同步执行代码:核心为RealCall的execute()方法

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {

Request request = new Request.Builder()

.url(url)

.build();

try (Response response = client.newCall(request).execute()) {

//即call.execute(),Call接口的具体实现为RealCall

return response.body().string();

}

}

RealCall的execute():

@Override public Response execute() throws IOException {

synchronized (this) {

if (executed) throw new IllegalStateException("Already Executed");

executed = true;

}

captureCallStackTrace();

try {

client.dispatcher().executed(this);//只是将call对象添加到Deque runningSyncCalls 中,只是增加了个引用

Response result = getResponseWithInterceptorChain();//真实执行的地方

if (result == null) throw new IOException("Canceled");

return result;

} finally {

client.dispatcher().finished(this);

}

}

getResponseWithInterceptorChain():

涉及到拦截器机制,参见:okhttp源码解析-coding skills-拦截器机制

Response getResponseWithInterceptorChain() throws IOException {

// Build a full stack of interceptors.

List interceptors = new ArrayList<>();

interceptors.addAll(client.interceptors());//用户设置的拦截器

interceptors.add(retryAndFollowUpInterceptor);//重定向和重试的拦截器

interceptors.add(new BridgeInterceptor(client.cookieJar()));//

interceptors.add(new CacheInterceptor(client.internalCache()));//缓存管理的拦截器

interceptors.add(new ConnectInterceptor(client));//Dns查询和获取socket连接的地方

if (!forWebSocket) {

interceptors.addAll(client.networkInterceptors());//用户设置的network拦截器

}

interceptors.add(new CallServerInterceptor(forWebSocket));//真实读写socket的地方

Interceptor.Chain chain = new RealInterceptorChain(

interceptors, null, null, null, 0, originalRequest);

return chain.proceed(originalRequest);

}

从文章开头,我们分析的请求流程:获取socket连接->往连接的流里读写数据 可以知道

请求流程的实现重点在两个拦截器:ConnectInterceptor和CallServerInterceptor

ConnectInterceptor

具体的DNS解析和一个网络连接的获取请参见 okhttp源码解析-网络框架业务处理-连接的建立与连接池

@Override public Response intercept(Chain chain) throws IOException {

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Request request = realChain.request();

StreamAllocation streamAllocation = realChain.streamAllocation();

// We need the network to satisfy this request. Possibly for validating a conditional GET.

boolean doExtensiveHealthChecks = !request.method().equals("GET");

HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);//httpCodec 封装了输入输出流

RealConnection connection = streamAllocation.connection();//RealConnection 封装了"网络连接"对象

return realChain.proceed(request, streamAllocation, httpCodec, connection);//将上面的对象封装到chain中

}

CallServerInterceptor: 真正读写socket流的地方

注: 这里输入输出流用的不是java的stream,而是okio,其中Sink相当于OutputStream,用于写请求,Source相当于InputStream,用于读取响应

@Override public Response intercept(Chain chain) throws IOException {

HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();//将输入输出流的封装对象从chain中拿出

StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();

Request request = chain.request();

long sentRequestMillis = System.currentTimeMillis();

httpCodec.writeRequestHeaders(request);//往流中写入请求头

Response.Builder responseBuilder = null;

if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {

// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100

// Continue" response before transmitting the request body. If we don't get that, return what

// we did get (such as a 4xx response) without ever transmitting the request body.

if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {

httpCodec.flushRequest();

responseBuilder = httpCodec.readResponseHeaders(true);//从流中读取响应头

}

// Write the request body, unless an "Expect: 100-continue" expectation failed.

if (responseBuilder == null) {

Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());

BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);//往输出流中写入请求体

bufferedRequestBody.close();

}

}

httpCodec.finishRequest();

if (responseBuilder == null) {

responseBuilder = httpCodec.readResponseHeaders(false);

}

Response response = responseBuilder

.request(request)

.handshake(streamAllocation.connection().handshake())

.sentRequestAtMillis(sentRequestMillis)

.receivedResponseAtMillis(System.currentTimeMillis())

.build();

int code = response.code();

if (forWebSocket && code == 101) {

// Connection is upgrading, but we need to ensure interceptors see a non-null response body.

response = response.newBuilder()

.body(Util.EMPTY_RESPONSE)

.build();

} else {

response = response.newBuilder()

.body(httpCodec.openResponseBody(response))//从流中读取响应体

.build();

}

if ("close".equalsIgnoreCase(response.request().header("Connection"))

|| "close".equalsIgnoreCase(response.header("Connection"))) {

streamAllocation.noNewStreams();

}

if ((code == 204 || code == 205) && response.body().contentLength() > 0) {

throw new ProtocolException(

"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());

}

return response;

}

从上面看到几个方法:

httpCodec.writeRequestHeaders(request);//往流中写入请求头

httpCodec.createRequestBody(request, request.body().contentLength())

httpCodec.readResponseHeaders(true);//从流中读取响应头

httpCodec.openResponseBody(response)

httpCodec 接口的实现类有Http1Codec和Http2Codec,我们看Http1Codec:

writeRequestHeaders:

@Override public void writeRequestHeaders(Request request) throws IOException {

String requestLine = RequestLine.get(

request, streamAllocation.connection().route().proxy().type());

writeRequest(request.headers(), requestLine);

}

/** Returns bytes of a request header for sending on an HTTP transport. */

public void writeRequest(Headers headers, String requestLine) throws IOException {

if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);

sink.writeUtf8(requestLine).writeUtf8("\r\n");

for (int i = 0, size = headers.size(); i < size; i++) {

sink.writeUtf8(headers.name(i))

.writeUtf8(": ")

.writeUtf8(headers.value(i))

.writeUtf8("\r\n");

}

sink.writeUtf8("\r\n");

state = STATE_OPEN_REQUEST_BODY;

}

读响应头:

@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {

if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {

throw new IllegalStateException("state: " + state);

}

try {

StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

Response.Builder responseBuilder = new Response.Builder()

.protocol(statusLine.protocol)

.code(statusLine.code)

.message(statusLine.message)

.headers(readHeaders());

if (expectContinue && statusLine.code == HTTP_CONTINUE) {

return null;

}

state = STATE_OPEN_RESPONSE_BODY;

return responseBuilder;

} catch (EOFException e) {

// Provide more context if the server ends the stream before sending a response.

IOException exception = new IOException("unexpected end of stream on " + streamAllocation);

exception.initCause(e);

throw exception;

}

}

/** Reads headers or trailers. */

public Headers readHeaders() throws IOException {

Headers.Builder headers = new Headers.Builder();

// parse the result headers until the first blank line

for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {

Internal.instance.addLenient(headers, line);

}

return headers.build();

}

写请求体:createRequestBody

@Override public Sink createRequestBody(Request request, long contentLength) {

if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {

// Stream a request body of unknown length.

return newChunkedSink();

}

if (contentLength != -1) {

// Stream a request body of a known length.

return newFixedLengthSink(contentLength);

}

throw new IllegalStateException(

"Cannot stream a request body without chunked encoding or a known content length!");

}

//读写: 在上方intercept中,传入

Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());

BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);

bufferedRequestBody.close();

读响应体:

@Override public ResponseBody openResponseBody(Response response) throws IOException {

Source source = getTransferStream(response);

return new RealResponseBody(response.headers(), Okio.buffer(source));

}

请求体RequestBody和响应体ResponseBody:

内部封装了输入输出流,具体的读写封装在特定的子类中,

RequestBody的子类有

表单提交的FormBody,文件上传的MultipartBody

FormBody往流中写数据的代码:

key和value要预先url编码成encodedName,encodedValue,放入两个list,然后根据index依次写如输出流

private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {

long byteCount = 0L;

Buffer buffer;

if (countBytes) {

buffer = new Buffer();

} else {

buffer = sink.buffer();

}

for (int i = 0, size = encodedNames.size(); i < size; i++) {

if (i > 0) buffer.writeByte('&');

buffer.writeUtf8(encodedNames.get(i));

buffer.writeByte('=');

buffer.writeUtf8(encodedValues.get(i));

}

if (countBytes) {

byteCount = buffer.size();

buffer.clear();

}

return byteCount;

}

MultipartBody 写数据的代码:

注意body也有content-type,这个与header中的content-type不一样,前者是标识这一块body是什么类,而header中的是标识这个http请求是什么类型.

而且,上传body里的content-type有特点的格式:MediaType.parse(type + "; boundary=" + boundary.utf8())

MultipartBody(ByteString boundary, MediaType type, List parts) {

this.boundary = boundary;

this.originalType = type;

this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());

this.parts = Util.immutableList(parts);

}

private static final byte[] COLONSPACE = {':', ' '};

private static final byte[] CRLF = {'\r', '\n'};

private static final byte[] DASHDASH = {'-', '-'};

private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {

long byteCount = 0L;

Buffer byteCountBuffer = null;

if (countBytes) {

sink = byteCountBuffer = new Buffer();

}

for (int p = 0, partCount = parts.size(); p < partCount; p++) {

Part part = parts.get(p);

Headers headers = part.headers;

RequestBody body = part.body;

sink.write(DASHDASH);

sink.write(boundary);

sink.write(CRLF);

if (headers != null) {

for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {

sink.writeUtf8(headers.name(h))

.write(COLONSPACE)

.writeUtf8(headers.value(h))

.write(CRLF);

}

}

MediaType contentType = body.contentType();

if (contentType != null) {

sink.writeUtf8("Content-Type: ")

.writeUtf8(contentType.toString())

.write(CRLF);

}

long contentLength = body.contentLength();

if (contentLength != -1) {

sink.writeUtf8("Content-Length: ")

.writeDecimalLong(contentLength)

.write(CRLF);

} else if (countBytes) {

// We can't measure the body's size without the sizes of its components.

byteCountBuffer.clear();

return -1L;

}

sink.write(CRLF);

if (countBytes) {

byteCount += contentLength;

} else {

body.writeTo(sink);

}

sink.write(CRLF);

}

sink.write(DASHDASH);

sink.write(boundary);

sink.write(DASHDASH);

sink.write(CRLF);

if (countBytes) {

byteCount += byteCountBuffer.size();

byteCountBuffer.clear();

}

return byteCount;

}

ResponseBody的子类有:

RealResponseBody和CacheResponseBody.前者代码少,主要实现在ResponseBody中:

提供有几个常用方法:

bytes():返回byte数组

string(): 返回字符串

byteStream(): 返回java的inputStream,可用于文件下载

public final byte[] bytes() throws IOException {

long contentLength = contentLength();

if (contentLength > Integer.MAX_VALUE) {

throw new IOException("Cannot buffer entire body for content length: " + contentLength);

}

BufferedSource source = source();

byte[] bytes;

try {

bytes = source.readByteArray();

} finally {

Util.closeQuietly(source);

}

if (contentLength != -1 && contentLength != bytes.length) {

throw new IOException("Content-Length ("

+ contentLength

+ ") and stream length ("

+ bytes.length

+ ") disagree");

}

return bytes;

}

public final String string() throws IOException {

BufferedSource source = source();

try {

Charset charset = Util.bomAwareCharset(source, charset());

return source.readString(charset);

} finally {

Util.closeQuietly(source);

}

}

public final InputStream byteStream() {

return source().inputStream();

}

Response: 用户拿到的最终返回对象

就是响应解析后变成内存对象,http报文各部分分别封装好了.并提供相应的一些方法

public final class Response implements Closeable {

final Request request;

final Protocol protocol;

final int code;

final String message;

final Handshake handshake;

final Headers headers;

final ResponseBody body;

final Response networkResponse;

final Response cacheResponse;

final Response priorResponse;

final long sentRequestAtMillis;

final long receivedResponseAtMillis;

private volatile CacheControl cacheControl; // Lazily initialized.

...

public boolean isSuccessful() {

return code >= 200 && code < 300;

}

public int code() {

return code;

}

public ResponseBody body() {

return body;

}

public List headers(String name) {

return headers.values(name);

}

public String header(String name) {

return header(name, null);

}

public String header(String name, String defaultValue) {

String result = headers.get(name);

return result != null ? result : defaultValue;

}

public Headers headers() {

return headers;

}

...

总结:

从上面的源码分析可知,okhttp的这一部分,本质上就是对http协议的一个实现,了解http协议,是读懂源码的一个前提.

直接使用okhttp时,涉及到两个类: Request和Response

okhttp提供了两种请求体的实现:表单提交时的FormBody,文件上传时的MultiPartBody

okhttp提供了响应体的几个接收形式:

以字符串形式接收:string()方法

以字节数组的形式接收: bytes()方法

以流的形式接收: byteStream()方法

封装到极致的网络库:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值