build
ClientExecChain execChain = new MainClientExec(
requestExec,
connManager,
reuseStrategy,
keepAliveStrategy,
targetAuthStrategy,
proxyAuthStrategy,
userTokenHandler);
execChain = new ProtocolExec(execChain, httpprocessor);
execChain = new RetryExec(execChain, retryHandler);
execChain = new RedirectExec(execChain, routePlanner, redirectStrategy);
RedirectExec
final RequestConfig config = context.getRequestConfig();
final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50;
HttpRoute currentRoute = route;
HttpRequestWrapper currentRequest = request;
for (int redirectCount = 0;;) {
final CloseableHttpResponse response = requestExecutor.execute(
currentRoute, currentRequest, context, execAware);
ProtocolExec
@Override
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException,
HttpException {
...
// Re-write request URI if needed
rewriteRequestURI(request, route);
final HttpParams params = request.getParams();
HttpHost virtualHost = (HttpHost) params.getParameter(ClientPNames.VIRTUAL_HOST);
HttpHost target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
this.httpProcessor.process(request, context);
final CloseableHttpResponse response = this.requestExecutor.execute(route, request,
context, execAware);
MainClientExec
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
if (execAware != null) {
if (execAware.isAborted()) {
connRequest.cancel();
throw new RequestAbortedException("Request aborted");
} else {
execAware.setCancellable(connRequest);
}
}
final int timeout = config.getConnectionRequestTimeout();
managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
execAware.setCancellable(connHolder);
if (!managedConn.isOpen()) {
establishRoute(proxyAuthState, managedConn, route, request, context);
}
final int timeout = config.getSocketTimeout();
managedConn.setSocketTimeout(timeout);
response = requestExecutor.execute(request, managedConn, context);
}
/**
* Establishes the target route.
*/
void establishRoute(
final AuthState proxyAuthState,
final HttpClientConnection managedConn,
final HttpRoute route,
final HttpRequest request,
final HttpClientContext context) throws HttpException, IOException {
final RequestConfig config = context.getRequestConfig();
final int timeout = config.getConnectTimeout();
final RouteTracker tracker = new RouteTracker(route);
int step;
do {
final HttpRoute fact = tracker.toRoute();
step = this.routeDirector.nextStep(route, fact);
switch (step) {
case HttpRouteDirector.CONNECT_TARGET:
this.connManager.connect(
managedConn,
route,
timeout > 0 ? timeout : 0,
context);
tracker.connectTarget(route.isSecure());
break;
case HttpRouteDirector.CONNECT_PROXY:
this.connManager.connect(
managedConn,
route,
timeout > 0 ? timeout : 0,
context);
final HttpHost proxy = route.getProxyHost();
tracker.connectProxy(proxy, false);
break;
case HttpRouteDirector.TUNNEL_TARGET: {
final boolean secure = createTunnelToTarget(
proxyAuthState, managedConn, route, request, context);
this.log.debug("Tunnel to target created.");
tracker.tunnelTarget(secure);
} break;
case HttpRouteDirector.TUNNEL_PROXY: {
// The most simple example for this case is a proxy chain
// of two proxies, where P1 must be tunnelled to P2.
// route: Source -> P1 -> P2 -> Target (3 hops)
// fact: Source -> P1 -> Target (2 hops)
final int hop = fact.getHopCount()-1; // the hop to establish
final boolean secure = createTunnelToProxy(route, hop, context);
this.log.debug("Tunnel to proxy created.");
tracker.tunnelProxy(route.getHopTarget(hop), secure);
} break;
case HttpRouteDirector.LAYER_PROTOCOL:
this.connManager.upgrade(managedConn, route, context);
tracker.layerProtocol(route.isSecure());
break;
case HttpRouteDirector.UNREACHABLE:
throw new HttpException("Unable to establish route: " +
"planned = " + route + "; current = " + fact);
case HttpRouteDirector.COMPLETE:
this.connManager.routeComplete(managedConn, route, context);
break;
default:
throw new IllegalStateException("Unknown step indicator "
+ step + " from RouteDirector.");
}
} while (step > HttpRouteDirector.COMPLETE);
}
HttpRequestExecutor
/**
* Sends the request and obtain a response.
*
* @param request the request to execute.
* @param conn the connection over which to execute the request.
*
* @return the response to the request.
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
public HttpResponse execute(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
try {
HttpResponse response = doSendRequest(request, conn, context);
if (response == null) {
response = doReceiveResponse(request, conn, context);
}
return response;
} catch (final IOException ex) {
closeConnection(conn);
throw ex;
} catch (final HttpException ex) {
closeConnection(conn);
throw ex;
} catch (final RuntimeException ex) {
closeConnection(conn);
throw ex;
}
}
/**
* Send the given request over the given connection.
* <p>
* This method also handles the expect-continue handshake if necessary.
* If it does not have to handle an expect-continue handshake, it will
* not use the connection for reading or anything else that depends on
* data coming in over the connection.
*
* @param request the request to send, already
* {@link #preProcess preprocessed}
* @param conn the connection over which to send the request,
* already established
* @param context the context for sending the request
*
* @return a terminal response received as part of an expect-continue
* handshake, or
* {@code null} if the expect-continue handshake is not used
*
* @throws IOException in case of an I/O error.
* @throws HttpException in case of HTTP protocol violation or a processing
* problem.
*/
protected HttpResponse doSendRequest(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
HttpResponse response = null;
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.FALSE);
conn.sendRequestHeader(request);
if (request instanceof HttpEntityEnclosingRequest) {
// Check for expect-continue handshake. We have to flush the
// headers and wait for an 100-continue response to handle it.
// If we get a different response, we must not send the entity.
boolean sendentity = true;
final ProtocolVersion ver =
request.getRequestLine().getProtocolVersion();
if (((HttpEntityEnclosingRequest) request).expectContinue() &&
!ver.lessEquals(HttpVersion.HTTP_1_0)) {
conn.flush();
// As suggested by RFC 2616 section 8.2.3, we don't wait for a
// 100-continue response forever. On timeout, send the entity.
if (conn.isResponseAvailable(this.waitForContinue)) {
response = conn.receiveResponseHeader();
if (canResponseHaveBody(request, response)) {
conn.receiveResponseEntity(response);
}
final int status = response.getStatusLine().getStatusCode();
if (status < 200) {
if (status != HttpStatus.SC_CONTINUE) {
throw new ProtocolException(
"Unexpected response: " + response.getStatusLine());
}
// discard 100-continue
response = null;
} else {
sendentity = false;
}
}
}
if (sendentity) {
conn.sendRequestEntity((HttpEntityEnclosingRequest) request);
}
}
conn.flush();
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
return response;
}
DefaultBHttpClientConnection
public void flush() throws IOException {
ensureOpen();
doFlush();
}
BHttpConnectionBase
/**
* This class serves as a base for all {@link HttpConnection} implementations and provides
* functionality common to both client and server HTTP connections.
*
* @since 4.0
*/
@NotThreadSafe
public class BHttpConnectionBase implements HttpConnection, HttpInetConnection {
private final SessionInputBufferImpl inbuffer;
private final SessionOutputBufferImpl outbuffer;
private final HttpConnectionMetricsImpl connMetrics;
private final ContentLengthStrategy incomingContentStrategy;
private final ContentLengthStrategy outgoingContentStrategy;
private volatile boolean open;
private volatile Socket socket;
protected void ensureOpen() throws IOException {
Asserts.check(this.open, "Connection is not open");
if (!this.inbuffer.isBound()) {
this.inbuffer.bind(getSocketInputStream(this.socket));
}
if (!this.outbuffer.isBound()) {
this.outbuffer.bind(getSocketOutputStream(this.socket));
}
}
protected void doFlush() throws IOException {
this.outbuffer.flush();
}
SessionOutputBufferImpl
/**
* Abstract base class for session output buffers that stream data to
* an arbitrary {@link OutputStream}. This class buffers small chunks of
* output data in an internal byte array for optimal output performance.
* </p>
* {@link #writeLine(CharArrayBuffer)} and {@link #writeLine(String)} methods
* of this class use CR-LF as a line delimiter.
*
* @since 4.3
*/
@NotThreadSafe
public class SessionOutputBufferImpl implements SessionOutputBuffer, BufferInfo {
private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF};
private final HttpTransportMetricsImpl metrics;
private final ByteArrayBuffer buffer;
private final int fragementSizeHint;
private final CharsetEncoder encoder;
private OutputStream outstream;
private ByteBuffer bbuf;
public void flush() throws IOException {
flushBuffer();
flushStream();
}
private void flushBuffer() throws IOException {
final int len = this.buffer.length();
if (len > 0) {
streamWrite(this.buffer.buffer(), 0, len);
this.buffer.clear();
this.metrics.incrementBytesTransferred(len);
}
}
private void streamWrite(final byte[] b, final int off, final int len) throws IOException {
Asserts.notNull(outstream, "Output stream");
this.outstream.write(b, off, len);
}
private void flushStream() throws IOException {
if (this.outstream != null) {
this.outstream.flush();
}
}
PoolingHttpClientConnectionManager
public ConnectionRequest requestConnection(
final HttpRoute route,
final Object state) {
Args.notNull(route, "HTTP route");
if (this.log.isDebugEnabled()) {
this.log.debug("Connection request: " + format(route, state) + formatStats(route));
}
final Future<CPoolEntry> future = this.pool.lease(route, state, null);
return new ConnectionRequest() {
@Override
public boolean cancel() {
return future.cancel(true);
}
@Override
public HttpClientConnection get(
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
return leaseConnection(future, timeout, tunit);
}
};
protected HttpClientConnection leaseConnection(
final Future<CPoolEntry> future,
final long timeout,
final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
final CPoolEntry entry;
try {
entry = future.get(timeout, tunit);
if (entry == null || future.isCancelled()) {
throw new InterruptedException();
}
Asserts.check(entry.getConnection() != null, "Pool entry with no connection");
if (this.log.isDebugEnabled()) {
this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
}
return CPoolProxy.newProxy(entry);
} catch (final TimeoutException ex) {
throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
}
}
public void connect(
final HttpClientConnection managedConn,
final HttpRoute route,
final int connectTimeout,
final HttpContext context) throws IOException {
Args.notNull(managedConn, "Managed Connection");
Args.notNull(route, "HTTP route");
final ManagedHttpClientConnection conn;
synchronized (managedConn) {
final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);
conn = entry.getConnection();
}
final HttpHost host;
if (route.getProxyHost() != null) {
host = route.getProxyHost();
} else {
host = route.getTargetHost();
}
final InetSocketAddress localAddress = route.getLocalSocketAddress();
SocketConfig socketConfig = this.configData.getSocketConfig(host);
if (socketConfig == null) {
socketConfig = this.configData.getDefaultSocketConfig();
}
if (socketConfig == null) {
socketConfig = SocketConfig.DEFAULT;
}
this.connectionOperator.connect(
conn, host, localAddress, connectTimeout, socketConfig, context);
}
@Override
public ManagedHttpClientConnection create(final HttpRoute route) throws IOException {
ConnectionConfig config = null;
if (route.getProxyHost() != null) {
config = this.configData.getConnectionConfig(route.getProxyHost());
}
if (config == null) {
config = this.configData.getConnectionConfig(route.getTargetHost());
}
if (config == null) {
config = this.configData.getDefaultConnectionConfig();
}
if (config == null) {
config = ConnectionConfig.DEFAULT;
}
return this.connFactory.create(route, config);
}
DefaultHttpClientConnectionOperator.connect()
@Override
public void connect(
final ManagedHttpClientConnection conn,
final HttpHost host,
final InetSocketAddress localAddress,
final int connectTimeout,
final SocketConfig socketConfig,
final HttpContext context) throws IOException {
final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
if (sf == null) {
throw new UnsupportedSchemeException(host.getSchemeName() +
" protocol is not supported");
}
final InetAddress[] addresses = host.getAddress() != null ?
new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());
final int port = this.schemePortResolver.resolve(host);
for (int i = 0; i < addresses.length; i++) {
final InetAddress address = addresses[i];
final boolean last = i == addresses.length - 1;
Socket sock = sf.createSocket(context);
sock.setSoTimeout(socketConfig.getSoTimeout());
sock.setReuseAddress(socketConfig.isSoReuseAddress());
sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
sock.setKeepAlive(socketConfig.isSoKeepAlive());
if (socketConfig.getRcvBufSize() > 0) {
sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
}
if (socketConfig.getSndBufSize() > 0) {
sock.setSendBufferSize(socketConfig.getSndBufSize());
}
final int linger = socketConfig.getSoLinger();
if (linger >= 0) {
sock.setSoLinger(true, linger);
}
conn.bind(sock);
final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
if (this.log.isDebugEnabled()) {
this.log.debug("Connecting to " + remoteAddress);
}
try {
sock = sf.connectSocket(
connectTimeout, sock, host, remoteAddress, localAddress, context);
conn.bind(sock);
if (this.log.isDebugEnabled()) {
this.log.debug("Connection established " + conn);
}
return;
} catch (final SocketTimeoutException ex) {
if (last) {
throw new ConnectTimeoutException(ex, host, addresses);
}
} catch (final ConnectException ex) {
if (last) {
final String msg = ex.getMessage();
if ("Connection timed out".equals(msg)) {
throw new ConnectTimeoutException(ex, host, addresses);
} else {
throw new HttpHostConnectException(ex, host, addresses);
}
}
} catch (final NoRouteToHostException ex) {
if (last) {
throw ex;
}
}
if (this.log.isDebugEnabled()) {
this.log.debug("Connect to " + remoteAddress + " timed out. " +
"Connection will be retried using another IP address");
}
}
}
AbstractConnPool
@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
public abstract class AbstractConnPool<T, C, E extends PoolEntry<T, C>>
implements ConnPool<T, E>, ConnPoolControl<T> {
private final Lock lock;
private final Condition condition;
private final ConnFactory<T, C> connFactory;
private final Map<T, RouteSpecificPool<T, C, E>> routeToPool;
private final Set<E> leased;
private final LinkedList<E> available;
private final LinkedList<Future<E>> pending;
private final Map<T, Integer> maxPerRoute;
private volatile boolean isShutDown;
private volatile int defaultMaxPerRoute;
private volatile int maxTotal;
private volatile int validateAfterInactivity;
lease()
/**
* {@inheritDoc}
* <p>
* Please note that this class does not maintain its own pool of execution
* {@link Thread}s. Therefore, one <b>must</b> call {@link Future#get()}
* or {@link Future#get(long, TimeUnit)} method on the {@link Future}
* returned by this method in order for the lease operation to complete.
*/
@Override
public Future<E> lease(final T route, final Object state, final FutureCallback<E> callback) {
Args.notNull(route, "Route");
Asserts.check(!this.isShutDown, "Connection pool shut down");
return new Future<E>() {
private volatile boolean cancelled;
private volatile boolean done;
private volatile E entry;
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
cancelled = true;
lock.lock();
try {
condition.signalAll();
} finally {
lock.unlock();
}
synchronized (this) {
final boolean result = !done;
done = true;
if (callback != null) {
callback.cancelled();
}
return result;
}
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean isDone() {
return done;
}
@Override
public E get() throws InterruptedException, ExecutionException {
try {
return get(0L, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
throw new ExecutionException(ex);
}
}
@Override
public E get(final long timeout, final TimeUnit tunit) throws InterruptedException, ExecutionException, TimeoutException {
if (entry != null) {
return entry;
}
synchronized (this) {
try {
for (;;) {
final E leasedEntry = getPoolEntryBlocking(route, state, timeout, tunit, this);
if (validateAfterInactivity > 0) {
if (leasedEntry.getUpdated() + validateAfterInactivity <= System.currentTimeMillis()) {
if (!validate(leasedEntry)) {
leasedEntry.close();
release(leasedEntry, false);
continue;
}
}
}
entry = leasedEntry;
done = true;
onLease(entry);
if (callback != null) {
callback.completed(entry);
}
return entry;
}
} catch (IOException ex) {
done = true;
if (callback != null) {
callback.failed(ex);
}
throw new ExecutionException(ex);
}
}
}
};
}
getPool(route)
private RouteSpecificPool<T, C, E> getPool(final T route) {
RouteSpecificPool<T, C, E> pool = this.routeToPool.get(route);
if (pool == null) {
pool = new RouteSpecificPool<T, C, E>(route) {
@Override
protected E createEntry(final C conn) {
return AbstractConnPool.this.createEntry(route, conn);
}
};
this.routeToPool.put(route, pool);
}
return pool;
}
private E getPoolEntryBlocking(
final T route, final Object state,
final long timeout, final TimeUnit tunit,
final Future<E> future) throws IOException, InterruptedException, TimeoutException {
Date deadline = null;
if (timeout > 0) {
deadline = new Date (System.currentTimeMillis() + tunit.toMillis(timeout));
}
this.lock.lock();
try {
final RouteSpecificPool<T, C, E> pool = getPool(route);
E entry;
for (;;) {
Asserts.check(!this.isShutDown, "Connection pool shut down");
for (;;) {
entry = pool.getFree(state);
if (entry == null) {
break;
}
if (entry.isExpired(System.currentTimeMillis())) {
entry.close();
}
if (entry.isClosed()) {
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
if (entry != null) {
this.available.remove(entry);
this.leased.add(entry);
onReuse(entry);
return entry;
}
// New connection is needed
final int maxPerRoute = getMax(route);
// Shrink the pool prior to allocating a new connection
final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
if (excess > 0) {
for (int i = 0; i < excess; i++) {
final E lastUsed = pool.getLastUsed();
if (lastUsed == null) {
break;
}
lastUsed.close();
this.available.remove(lastUsed);
pool.remove(lastUsed);
}
}
if (pool.getAllocatedCount() < maxPerRoute) {
final int totalUsed = this.leased.size();
final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
if (freeCapacity > 0) {
final int totalAvailable = this.available.size();
if (totalAvailable > freeCapacity - 1) {
if (!this.available.isEmpty()) {
final E lastUsed = this.available.removeLast();
lastUsed.close();
final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute());
otherpool.remove(lastUsed);
}
}
final C conn = this.connFactory.create(route);
entry = pool.add(conn);
this.leased.add(entry);
return entry;
}
}
boolean success = false;
try {
if (future.isCancelled()) {
throw new InterruptedException("Operation interrupted");
}
pool.queue(future);
this.pending.add(future);
if (deadline != null) {
success = this.condition.awaitUntil(deadline);
} else {
this.condition.await();
success = true;
}
if (future.isCancelled()) {
throw new InterruptedException("Operation interrupted");
}
} finally {
// In case of 'success', we were woken up by the
// connection pool and should now have a connection
// waiting for us, or else we're shutting down.
// Just continue in the loop, both cases are checked.
pool.unqueue(future);
this.pending.remove(future);
}
// check for spurious wakeup vs. timeout
if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) {
break;
}
}
throw new TimeoutException("Timeout waiting for connection");
} finally {
this.lock.unlock();
}
}
abstract class RouteSpecificPool<T, C, E extends PoolEntry<T, C>> {
private final T route;
private final Set<E> leased;
private final LinkedList<E> available;
private final LinkedList<Future<E>> pending;
RouteSpecificPool(final T route) {
super();
this.route = route;
this.leased = new HashSet<E>();
this.available = new LinkedList<E>();
this.pending = new LinkedList<Future<E>>();
}
protected abstract E createEntry(C conn);
public final T getRoute() {
return route;
}
public int getLeasedCount() {
return this.leased.size();
}
public int getPendingCount() {
return this.pending.size();
}
public int getAvailableCount() {
return this.available.size();
}
public int getAllocatedCount() {
return this.available.size() + this.leased.size();
}
public E getFree(final Object state) {
if (!this.available.isEmpty()) {
if (state != null) {
final Iterator<E> it = this.available.iterator();
while (it.hasNext()) {
final E entry = it.next();
if (state.equals(entry.getState())) {
it.remove();
this.leased.add(entry);
return entry;
}
}
}
final Iterator<E> it = this.available.iterator();
while (it.hasNext()) {
final E entry = it.next();
if (entry.getState() == null) {
it.remove();
this.leased.add(entry);
return entry;
}
}
}
return null;
}
public E getLastUsed() {
if (!this.available.isEmpty()) {
return this.available.getLast();
} else {
return null;
}
}
public boolean remove(final E entry) {
Args.notNull(entry, "Pool entry");
if (!this.available.remove(entry)) {
if (!this.leased.remove(entry)) {
return false;
}
}
return true;
}
public void free(final E entry, final boolean reusable) {
Args.notNull(entry, "Pool entry");
final boolean found = this.leased.remove(entry);
Asserts.check(found, "Entry %s has not been leased from this pool", entry);
if (reusable) {
this.available.addFirst(entry);
}
}
public E add(final C conn) {
final E entry = createEntry(conn);
this.leased.add(entry);
return entry;
}
public void queue(final Future<E> future) {
if (future == null) {
return;
}
this.pending.add(future);
}
public Future<E> nextPending() {
return this.pending.poll();
}
public void unqueue(final Future<E> future) {
if (future == null) {
return;
}
this.pending.remove(future);
}
public void shutdown() {
for (final Future<E> future: this.pending) {
future.cancel(true);
}
this.pending.clear();
for (final E entry: this.available) {
entry.close();
}
this.available.clear();
for (final E entry: this.leased) {
entry.close();
}
this.leased.clear();
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append("[route: ");
buffer.append(this.route);
buffer.append("][leased: ");
buffer.append(this.leased.size());
buffer.append("][available: ");
buffer.append(this.available.size());
buffer.append("][pending: ");
buffer.append(this.pending.size());
buffer.append("]");
return buffer.toString();
}
}
arr$ = {HttpRequestInterceptor[9]@2551}
0 = {RequestDefaultHeaders@2553}
1 = {RequestContent@2565}
2 = {RequestTargetHost@2571}
3 = {RequestClientConnControl@2596}
4 = {RequestUserAgent@2617}
5 = {RequestExpectContinue@2631}
6 = {RequestAddCookies@2632}
7 = {RequestAcceptEncoding@2633}
8 = {RequestAuthCache@2634}
网友评论