http协议相关
一个http请求的过程
- 输入url和参数
- ->DNS解析(访问域名服务器53号端口,根据域名拿到ip,可能会拿到好几个ip)
- ->根据ip和端口号连接socket(TCP三次握手已封装在socket api内部,对开发者透明)
- -> socket连接成功后,往socket输出流中写入http报文
一个响应的接收过程
- 服务器接受请求并处理后发出响应
- 客户端从socket输入流中读取http报文
注意: 请求行/状态行和报文头部是字符,而请求体和响应体可以是字符,也可以是二进制流
http请求报文的格式
Paste_Image.png
注: 下图第一行名字标错了,是叫请求行.状态行是响应报文中的叫法.
Paste_Image.png
特殊一点的: 文件上传的http报文格式:
注:下图请求参数为:
键值对:
“uploadFile555″,”1474363536041.jpg”
“api_secret777″,”898767hjk”
文件:
“uploadFile”,”/storage/emulated/0/qxinli.apk”
upload
http响应报文的格式
Paste_Image.png
Paste_Image.png
常见的json response:
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<RealCall> 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<Interceptor> 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<Part> 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<String> 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()方法
参考:
一个http请求的详细过程
HTTP协议详解(真的很经典)