概要
我们平时使用最多的请求方式莫过于GET和POST请求,它们几乎完成我们平时所需的基本任务,下面我们分析一下它们的区别以及在
OkHttp
中的调用流程
GET和POST区别
GET
只接受ASCII字符,而POST
没有限制
GET
请求只能进行URL
编码,而POST
支持多种编码方式
GET
比POST
更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息,若想保证传输安全,可通过HTTPS
进行加密
GET
请求产生一个TCP
数据包;POST
产生两个TCP
数据包;对于GET
方式的请求,浏览器会把Header
和Data
一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送Header,服务器响应100 continue,浏览器再发送Data
,服务器响应200(返回数据)
GET源码解析
GET同步流程解析
GET流程图.jpg[ 1 ]外部操作
public class GetExample {
//声明OkHttpClick实例
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
//声明Request实例,组织所需元素数据
Request request = new Request.Builder()
.url(url)
.build();
//通过newCall得到一个RealCall实例,同时也声明了一个 Transmitter 发射器的实例
//调取execute()进行执行
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
[ 2 ] 进入RelCall.kt ,执行 @方法
execute()
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
//设置超时
transmitter.timeoutEnter()
//此方法是个‘钩子’,若在外部没有具体实现,则不执行任何操作
transmitter.callStart()
try {
//将当前的realCall存入runningSyncCalls链表
client.dispatcher().executed(this)
//调取拦截链,实现数据访问,返回一个Response对象
return getResponseWithInterceptorChain()
} finally {
client.dispatcher().finished(this)
}
}
[3] 调取拦截链的实现我们不在此处过多赘述,详情请见我的另一篇文章okHttp框架分析--拦截链
GET异步请求
[1]外部操作
public final class AsynchronousGet {
//OkHttp客户端实例
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
//请求实例
Request request = new Request.Builder()
.url("https://raw.github.com/square/okhttp/master/README.md") //url网址配置
.build();
//[ 2 ] client new一个RealCall,调用enqueue函数进入队列,并实现回调操作
client.newCall(request).enqueue(new Callback() {
//失败回调实现
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace(); //打印访问服务失败的异常信息
}
//成功回调实现
@Override public void onResponse(Call call, Response response) throws IOException {
...
//todo 访问服务器成果后的具体操作
});
}
[ 2 ]
client
声明一个RealCall
类型的对象,调用enqueue(...)
函数进入队列,并实现回调操作
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
//发射器开始调用,此处为一个空操作,可在外部进行操作重写
transmitter.callStart()
//[ 3 ]client事件分发处理,获取一个Dispatcher对象,
//继续调用enqueue函数,传入回调Callback包装成AsyncCall类型
client.dispatcher().enqueue(AsyncCall(responseCallback))
}
[ 3 ]
client
事件分发处理,获取一个Dispatcher
类型对象,继续调用enqueue(...)
函数,传入回调Callback
包装成AsyncCall
类型
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//将我们的callback回调添加进准备异步调用的队列
readyAsyncCalls.add(call)
//修改AsyncCall,使其共享现有运行调用的AtomicInteger 相同的主机
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//[ 4 ] 将符合条件的调用从[readyAsyncCalls]提升到[runningAsyncCalls],并在线程池执行。
//必须不同步调用,因为执行调用可以调用在用户代码
promoteAndExecute()
}
[ 4 ] 将符合条件的调用从
readyAsyncCalls
链表提升到runningAsyncCalls
链表,并在线程池执行。执行方法promoteAndExecute()
private fun promoteAndExecute(): Boolean {
//断言 当先线程是否持有Lock锁
assert(!Thread.holdsLock(this))
//声明私有的一个执行回调队列
val executableCalls = ArrayList<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // 运行异步调用最大容量
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // 主机调用最大容量
i.remove()
//此处为原子操作,将主机的调用个数增加 1
asyncCall.callsPerHost().incrementAndGet()
//将我们的Callback添加到执行回调队列
executableCalls.add(asyncCall)
//将我们的Callback添加到异步运行回调队列
runningAsyncCalls.add(asyncCall)
}
//正在运行的异步回调和同步回调个数相加是否大于 0
isRunning = runningCallsCount() > 0
}
//循环私有执行回调队列
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//[ 5 ] 尝试在“executorService”上注册此异步调用,获取一个线程池作为参数
asyncCall.executeOn(executorService())
}
return isRunning
}
[ 5 ] 尝试在
executorService
上注册此异步调用,获取一个线程池作为参数,执行方法asyncCall.executeOn(executorService())
fun executeOn(executorService: ExecutorService) {
//判定当前线程是否持有Lock锁
assert(!Thread.holdsLock(client.dispatcher()))
var success = false
try {
//[ 6 ] 执行线程
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
transmitter.noMoreExchanges(ioException)
//执行发生异常则回调 onFailure函数
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher().finished(this) // This call is no longer running!
}
}
}
[ 6 ]最终还是进入方法
execute()
, 执行线程操作发起对服务器的请求操作
override fun execute() {
var signalledCallback = false
//发射器判断超时设置
transmitter.timeoutEnter()
try {
///调取拦截链,实现数据访问,返回一个Response对象
val response = getResponseWithInterceptorChain()
signalledCallback = true
//访问成功则调取回调接口 onResponse函数
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for ${toLoggableString()}", e)
} else {
//访问发生异常则 回调接口 onFailure函数
responseCallback.onFailure(this@RealCall, e)
}
} finally {
//最终结束本次调用
client.dispatcher().finished(this)
}
}
}
POST源码解析
POST同步流程解析
POST流程图.jpg[ 1 ]外部操作
//请求体类型
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("text/x-markdown; charset=utf-8");
//声明OkHttpClient实例
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
//请求体文件
File file = new File("README.md");
//声明一个 请求实例
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw") //url连接
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)) //post请求方法,传入一个请求体参数
.build();
//new 一个 RealCall实例,调用 @方法[ execute() ]函数,执行数据访问
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
[ 2 ] 此处说明一下,
POST
同步请求流程与GET
同步请求流程一般无二,最终会在真正发起服务的拦截器CallServerInterceptor.kt做具体的操作或者在自定拦截器中对不同的请求方法做特殊处理;进入 CallServerInterceptor.kt 类,执行方法intercept(chain: Interceptor.Chain)
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange()
//获取请求实例
val request = realChain.request()
//获取请求体
val requestBody = request.body()
...
//@重点 做判定,若当前请求方法不是 GET或 HEAD 并且 请求体不为 Null 则判定成立
if (HttpMethod.permitsRequestBody(request.method()) && requestBody != null) {
//如果请求上有一个“Expect: 100-continue”报头,则等待一个“HTTP/1.1 100”报头
//在发送请求体之前继续“响应”。如果我们没有收到,返回
//在没有传输请求体的情况下,我们确实得到了(例如4xx响应)
//在[预用知识说明]标签 ==> { “Expect: 100-continue”的来龙去脉 } 中做详细说明
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseHeadersStarted = true
//开始调用响应头
exchange.responseHeadersStart()
// 读取响应头
responseBuilder = exchange.readResponseHeaders(true)
}
if (responseBuilder == null) {
if (requestBody.isDuplex()) {
// 准备一个双重主体,以便应用程序稍后可以发送请求主体。
exchange.flushRequest()
//创建一个buffer
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
//请求体写入buff中
requestBody.writeTo(bufferedRequestBody)
} else {
// 如果满足“Expect: 100-continue”期望,则编写请求主体
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
if (!exchange.connection().isMultiplexed) {
//如果没有满足“Expect: 100-continue”期望,请阻止HTTP/1连接
//避免重复使用。否则,我们仍然有义务将请求体传输到
//让连接保持一致的状态。
exchange.noNewExchangesOnConnection()
}
}
} else {
//没有请求体
exchange.noRequestBody()
}
if (requestBody == null || !requestBody.isDuplex()) {
//将所有缓冲数据写入底层接收器(如果存在的话)。然后这个sink是递归不断刷新
//将数据尽可能地推送到最终目的地。通常, 目标是一个网络套接字或文件。
exchange.finishRequest()
}
if (!responseHeadersStarted) {
//仅在接收响应头之前调用。
// 连接是隐式的,通常与最后一个[connectionAcquired]事件相关。
//这可以被调用超过1次为一个[Call]。
exchange.responseHeadersStart()
}
if (responseBuilder == null) {
//从HTTP传输解析响应头的字节。
responseBuilder = exchange.readResponseHeaders(false)!!
}
//一个HTTP响应。
//该类的实例不是不可变的:响应体是一次性的
//只使用一次,然后关闭的值。所有其他属性都是不可变的。
var response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code() //获取响应 Code
if (code == 100) {
// 服务器发送了100-continue,即使我们没有请求。
//再读一遍实际的响应
response = exchange.readResponseHeaders(false)!!
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code()
}
//在接收响应标头后立即调用
exchange.responseHeadersEnd(response)
response = if (forWebSocket && code == 101) {
//连接叠积,但我们需要确保拦截器看到非空响应体。
response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
//关闭连接
if ("close".equals(response.request().header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
//抛出连接异常
if ((code == 204 || code == 205) && response.body()?.contentLength() ?: -1 > 0) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body()?.contentLength()}")
}
//返回响应数据
return response
}
POST 异步请求
与GET异步操作一般无二,同样是在真正发起服务的拦截器CallServerInterceptor.kt做具体的操作,这里不在赘述
预用知识说明
-
1)“Expect: 100-continue”的来龙去脉:
HTTP/1.1 协议 里设计100 (Continue)
HTTP 状态码的的目的是,在客户端发送Request Message
之前,HTTP/1.1 协议
允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于Request Headers
)即,Client
和Server
在Post
(较大)数据之前,允许双方“握手”,如果匹配上了,Client
才开始发送(较大)数据。这么做的原因是,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。
协议对
HTTP/1.1 clients
的要求是:
如果client
预期等待100-continue
的应答,那么它发的请求必须包含一个Expect: 100-continue
的头域!
2)libcurl
发送大于1024字节 数据时启用Expect:100-continue
特性:
这也就是 Laruence
在 2011 年撰文所写的:
在使用 curl
做 POST
的时候,当要 POST
的数据大于 1024 字节的时候,curl
并不会直接就发起 POST
请求,而是会分为两步:
- 发送一个请求,包含一个
Expect: 100-continue
头域,询问Server
是否愿意接收数据;
- 接收到
Server
返回的100-continue
应答以后,才把数据POST
给Server
;这是 libcurl 的行为。
This ALL! Thanks EveryBody!
网友评论