mirror of
https://github.com/square/okhttp.git
synced 2026-01-25 16:01:38 +03:00
Merge pull request #382 from square/jwilson_1230_requestline
Move request line and status line out of RawHeaders.
This commit is contained in:
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package com.squareup.okhttp.mockwebserver;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
@@ -24,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class CustomDispatcherTest extends TestCase {
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||
import com.squareup.okhttp.internal.http.HttpTransport;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import com.squareup.okhttp.internal.http.SpdyTransport;
|
||||
import com.squareup.okhttp.internal.spdy.SpdyConnection;
|
||||
import java.io.BufferedInputStream;
|
||||
@@ -32,7 +31,6 @@ import java.io.OutputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
@@ -309,23 +307,23 @@ public final class Connection implements Closeable {
|
||||
* retried if the proxy requires authorization.
|
||||
*/
|
||||
private void makeTunnel(TunnelRequest tunnelRequest) throws IOException {
|
||||
RawHeaders requestHeaders = tunnelRequest.getRequestHeaders();
|
||||
Request request = tunnelRequest.getRequest();
|
||||
String requestLine = tunnelRequest.requestLine();
|
||||
while (true) {
|
||||
out.write(requestHeaders.toBytes());
|
||||
RawHeaders responseHeaders = RawHeaders.readHttpHeaders(in);
|
||||
out.write(request.rawHeaders().toBytes(requestLine));
|
||||
Response response = HttpTransport.readResponse(request, in).build();
|
||||
|
||||
switch (responseHeaders.getResponseCode()) {
|
||||
switch (response.code()) {
|
||||
case HTTP_OK:
|
||||
return;
|
||||
case HTTP_PROXY_AUTH:
|
||||
URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
|
||||
requestHeaders = HttpAuthenticator.processAuthHeader(route.address.authenticator,
|
||||
HTTP_PROXY_AUTH, responseHeaders, requestHeaders, route.proxy, url);
|
||||
if (requestHeaders != null) continue;
|
||||
request = HttpAuthenticator.processAuthHeader(
|
||||
route.address.authenticator, response, route.proxy);
|
||||
if (request != null) continue;
|
||||
throw new IOException("Failed to authenticate with proxy");
|
||||
default:
|
||||
throw new IOException(
|
||||
"Unexpected response code for CONNECT: " + responseHeaders.getResponseCode());
|
||||
"Unexpected response code for CONNECT: " + response.code());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +358,7 @@ public final class HttpResponseCache extends ResponseCache implements OkResponse
|
||||
private final String url;
|
||||
private final RawHeaders varyHeaders;
|
||||
private final String requestMethod;
|
||||
private final String statusLine;
|
||||
private final RawHeaders responseHeaders;
|
||||
private final Handshake handshake;
|
||||
|
||||
@@ -422,8 +423,8 @@ public final class HttpResponseCache extends ResponseCache implements OkResponse
|
||||
}
|
||||
varyHeaders = varyHeadersBuilder.build();
|
||||
|
||||
statusLine = reader.readLine();
|
||||
RawHeaders.Builder responseHeadersBuilder = new RawHeaders.Builder();
|
||||
responseHeadersBuilder.setStatusLine(reader.readLine());
|
||||
int responseHeaderLineCount = reader.readInt();
|
||||
for (int i = 0; i < responseHeaderLineCount; i++) {
|
||||
responseHeadersBuilder.addLine(reader.readLine());
|
||||
@@ -451,6 +452,7 @@ public final class HttpResponseCache extends ResponseCache implements OkResponse
|
||||
this.url = response.request().urlString();
|
||||
this.varyHeaders = response.request().rawHeaders().getAll(response.getVaryFields());
|
||||
this.requestMethod = response.request().method();
|
||||
this.statusLine = response.statusLine();
|
||||
this.responseHeaders = response.rawHeaders();
|
||||
this.handshake = response.handshake();
|
||||
}
|
||||
@@ -466,7 +468,7 @@ public final class HttpResponseCache extends ResponseCache implements OkResponse
|
||||
writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n');
|
||||
}
|
||||
|
||||
writer.write(responseHeaders.getStatusLine() + '\n');
|
||||
writer.write(statusLine + '\n');
|
||||
writer.write(Integer.toString(responseHeaders.length()) + '\n');
|
||||
for (int i = 0; i < responseHeaders.length(); i++) {
|
||||
writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n');
|
||||
@@ -525,7 +527,8 @@ public final class HttpResponseCache extends ResponseCache implements OkResponse
|
||||
public Response response(Request request, DiskLruCache.Snapshot snapshot) {
|
||||
String contentType = responseHeaders.get("Content-Type");
|
||||
String contentLength = responseHeaders.get("Content-Length");
|
||||
return new Response.Builder(request, responseHeaders.getResponseCode())
|
||||
return new Response.Builder(request)
|
||||
.statusLine(statusLine)
|
||||
.rawHeaders(responseHeaders)
|
||||
.body(new CacheResponseBody(snapshot, contentType, contentLength))
|
||||
.handshake(handshake)
|
||||
|
||||
@@ -18,9 +18,7 @@ package com.squareup.okhttp;
|
||||
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||
import com.squareup.okhttp.internal.http.HttpEngine;
|
||||
import com.squareup.okhttp.internal.http.HttpTransport;
|
||||
import com.squareup.okhttp.internal.http.HttpsEngine;
|
||||
import com.squareup.okhttp.internal.http.Policy;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.Proxy;
|
||||
@@ -63,10 +61,6 @@ final class Job implements Runnable, Policy {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public URL getURL() {
|
||||
return request.url();
|
||||
}
|
||||
|
||||
@Override public long getIfModifiedSince() {
|
||||
return 0; // For HttpURLConnection only. We let the cache drive this.
|
||||
}
|
||||
@@ -146,15 +140,7 @@ final class Job implements Runnable, Policy {
|
||||
}
|
||||
|
||||
HttpEngine newEngine(Connection connection) throws IOException {
|
||||
String protocol = request.url().getProtocol();
|
||||
RawHeaders requestHeaders = request.rawHeaders();
|
||||
if (protocol.equals("http")) {
|
||||
return new HttpEngine(client, this, request.method(), requestHeaders, connection, null);
|
||||
} else if (protocol.equals("https")) {
|
||||
return new HttpsEngine(client, this, request.method(), requestHeaders, connection, null);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return new HttpEngine(client, this, request, connection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,12 +163,8 @@ final class Job implements Runnable, Policy {
|
||||
}
|
||||
// fall-through
|
||||
case HTTP_UNAUTHORIZED:
|
||||
RawHeaders successorRequestHeaders = HttpAuthenticator.processAuthHeader(
|
||||
client.getAuthenticator(), response.code(), response.rawHeaders(), request.rawHeaders(),
|
||||
selectedProxy, this.request.url());
|
||||
return successorRequestHeaders != null
|
||||
? request.newBuilder().rawHeaders(successorRequestHeaders).build()
|
||||
: null;
|
||||
return HttpAuthenticator.processAuthHeader(
|
||||
client.getAuthenticator(), response, selectedProxy);
|
||||
|
||||
case HTTP_MULT_CHOICE:
|
||||
case HTTP_MOVED_PERM:
|
||||
|
||||
@@ -216,6 +216,10 @@ public final class Request {
|
||||
return result != null ? result : (parsedHeaders = new ParsedHeaders(headers));
|
||||
}
|
||||
|
||||
public boolean isHttps() {
|
||||
return url().getProtocol().equals("https");
|
||||
}
|
||||
|
||||
/** Parsed request headers, computed on-demand and cached. */
|
||||
private static class ParsedHeaders {
|
||||
/** Don't use a cache to satisfy this request. */
|
||||
@@ -445,12 +449,6 @@ public final class Request {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: this shouldn't be public.
|
||||
public Builder setRequestLine(String requestLine) {
|
||||
headers.setRequestLine(requestLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setChunked() {
|
||||
headers.set("Transfer-Encoding", "chunked");
|
||||
return this;
|
||||
@@ -527,11 +525,11 @@ public final class Request {
|
||||
public Builder addSpdyRequestHeaders(
|
||||
String method, String path, String version, String host, String scheme) {
|
||||
// TODO: populate the statusLine for the client's benefit?
|
||||
headers.add(":method", method);
|
||||
headers.add(":scheme", scheme);
|
||||
headers.add(":path", path);
|
||||
headers.add(":version", version);
|
||||
headers.add(":host", host);
|
||||
headers.set(":method", method);
|
||||
headers.set(":scheme", scheme);
|
||||
headers.set(":path", path);
|
||||
headers.set(":version", version);
|
||||
headers.set(":host", host);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.HeaderParser;
|
||||
import com.squareup.okhttp.internal.http.HttpDate;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import com.squareup.okhttp.internal.http.StatusLine;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@@ -62,7 +63,7 @@ public final class Response {
|
||||
= Platform.get().getPrefix() + "-Selected-Transport";
|
||||
|
||||
private final Request request;
|
||||
private final int code;
|
||||
private final StatusLine statusLine;
|
||||
private final Handshake handshake;
|
||||
private final RawHeaders headers;
|
||||
private final Body body;
|
||||
@@ -72,7 +73,7 @@ public final class Response {
|
||||
|
||||
private Response(Builder builder) {
|
||||
this.request = builder.request;
|
||||
this.code = builder.code;
|
||||
this.statusLine = builder.statusLine;
|
||||
this.handshake = builder.handshake;
|
||||
this.headers = builder.headers.build();
|
||||
this.body = builder.body;
|
||||
@@ -95,8 +96,20 @@ public final class Response {
|
||||
return request;
|
||||
}
|
||||
|
||||
public String statusLine() {
|
||||
return statusLine.getStatusLine();
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
return statusLine.code();
|
||||
}
|
||||
|
||||
public String statusMessage() {
|
||||
return statusLine.message();
|
||||
}
|
||||
|
||||
public int httpMinorVersion() {
|
||||
return statusLine.httpMinorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +159,8 @@ public final class Response {
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
return new Builder(request, code)
|
||||
return new Builder(request)
|
||||
.statusLine(statusLine)
|
||||
.handshake(handshake)
|
||||
.rawHeaders(headers)
|
||||
.body(body)
|
||||
@@ -265,7 +279,7 @@ public final class Response {
|
||||
* network response should be used.
|
||||
*/
|
||||
public boolean validate(Response network) {
|
||||
if (network.headers.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
if (network.code() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -288,7 +302,6 @@ public final class Response {
|
||||
*/
|
||||
public Response combine(Response network) throws IOException {
|
||||
RawHeaders.Builder result = new RawHeaders.Builder();
|
||||
result.setStatusLine(headers.getStatusLine());
|
||||
|
||||
for (int i = 0; i < headers.length(); i++) {
|
||||
String fieldName = headers.getFieldName(i);
|
||||
@@ -604,17 +617,29 @@ public final class Response {
|
||||
|
||||
public static class Builder {
|
||||
private final Request request;
|
||||
private final int code;
|
||||
private StatusLine statusLine;
|
||||
private Handshake handshake;
|
||||
private RawHeaders.Builder headers = new RawHeaders.Builder();
|
||||
private Body body;
|
||||
private Response redirectedBy;
|
||||
|
||||
public Builder(Request request, int code) {
|
||||
public Builder(Request request) {
|
||||
if (request == null) throw new IllegalArgumentException("request == null");
|
||||
if (code <= 0) throw new IllegalArgumentException("code <= 0");
|
||||
this.request = request;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Builder statusLine(StatusLine statusLine) {
|
||||
if (statusLine == null) throw new IllegalArgumentException("statusLine == null");
|
||||
this.statusLine = statusLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder statusLine(String statusLine) {
|
||||
try {
|
||||
return statusLine(new StatusLine(statusLine));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder handshake(Handshake handshake) {
|
||||
@@ -677,7 +702,7 @@ public final class Response {
|
||||
|
||||
// TODO: this shouldn't be public.
|
||||
public Builder setResponseSource(ResponseSource responseSource) {
|
||||
headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + headers.getResponseCode());
|
||||
headers.set(RESPONSE_SOURCE, responseSource.toString() + " " + statusLine.code());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -688,8 +713,8 @@ public final class Response {
|
||||
}
|
||||
|
||||
public Response build() {
|
||||
if (request == null) throw new IllegalStateException("Response has no request.");
|
||||
if (code == -1) throw new IllegalStateException("Response has no code.");
|
||||
if (request == null) throw new IllegalStateException("request == null");
|
||||
if (statusLine == null) throw new IllegalStateException("statusLine == null");
|
||||
return new Response(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.getDefaultPort;
|
||||
|
||||
@@ -49,27 +50,30 @@ public final class TunnelRequest {
|
||||
this.proxyAuthorization = proxyAuthorization;
|
||||
}
|
||||
|
||||
String requestLine() {
|
||||
return "CONNECT " + host + ":" + port + " HTTP/1.1";
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're creating a TLS tunnel, send only the minimum set of headers.
|
||||
* This avoids sending potentially sensitive data like HTTP cookies to
|
||||
* the proxy unencrypted.
|
||||
*/
|
||||
RawHeaders getRequestHeaders() {
|
||||
RawHeaders.Builder result = new RawHeaders.Builder()
|
||||
.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1");
|
||||
Request getRequest() throws IOException {
|
||||
Request.Builder result = new Request.Builder(new URL("https", host, port, "/"));
|
||||
|
||||
// Always set Host and User-Agent.
|
||||
result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
|
||||
result.set("User-Agent", userAgent);
|
||||
result.header("Host", port == getDefaultPort("https") ? host : (host + ":" + port));
|
||||
result.header("User-Agent", userAgent);
|
||||
|
||||
// Copy over the Proxy-Authorization header if it exists.
|
||||
if (proxyAuthorization != null) {
|
||||
result.set("Proxy-Authorization", proxyAuthorization);
|
||||
result.header("Proxy-Authorization", proxyAuthorization);
|
||||
}
|
||||
|
||||
// Always set the Proxy-Connection to Keep-Alive for the benefit of
|
||||
// HTTP/1.0 proxies like Squid.
|
||||
result.set("Proxy-Connection", "Keep-Alive");
|
||||
result.header("Proxy-Connection", "Keep-Alive");
|
||||
return result.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.OkAuthenticator;
|
||||
import com.squareup.okhttp.OkAuthenticator.Challenge;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetAddress;
|
||||
@@ -84,32 +86,33 @@ public final class HttpAuthenticator {
|
||||
|
||||
/**
|
||||
* React to a failed authorization response by looking up new credentials.
|
||||
* Returns headers for a subsequent attempt, or null if no further attempts
|
||||
* Returns a request for a subsequent attempt, or null if no further attempts
|
||||
* should be made.
|
||||
*/
|
||||
public static RawHeaders processAuthHeader(OkAuthenticator authenticator, int responseCode,
|
||||
RawHeaders responseHeaders, RawHeaders requestHeaders, Proxy proxy, URL url)
|
||||
throws IOException {
|
||||
public static Request processAuthHeader(
|
||||
OkAuthenticator authenticator, Response response, Proxy proxy) throws IOException {
|
||||
String responseField;
|
||||
String requestField;
|
||||
if (responseCode == HTTP_UNAUTHORIZED) {
|
||||
if (response.code() == HTTP_UNAUTHORIZED) {
|
||||
responseField = "WWW-Authenticate";
|
||||
requestField = "Authorization";
|
||||
} else if (responseCode == HTTP_PROXY_AUTH) {
|
||||
} else if (response.code() == HTTP_PROXY_AUTH) {
|
||||
responseField = "Proxy-Authenticate";
|
||||
requestField = "Proxy-Authorization";
|
||||
} else {
|
||||
throw new IllegalArgumentException(); // TODO: ProtocolException?
|
||||
}
|
||||
List<Challenge> challenges = parseChallenges(responseHeaders, responseField);
|
||||
List<Challenge> challenges = parseChallenges(response.rawHeaders(), responseField);
|
||||
if (challenges.isEmpty()) return null; // Could not find a challenge so end the request cycle.
|
||||
Credential credential = responseHeaders.getResponseCode() == HTTP_PROXY_AUTH
|
||||
? authenticator.authenticateProxy(proxy, url, challenges)
|
||||
: authenticator.authenticate(proxy, url, challenges);
|
||||
|
||||
Request request = response.request();
|
||||
Credential credential = response.code() == HTTP_PROXY_AUTH
|
||||
? authenticator.authenticateProxy(proxy, request.url(), challenges)
|
||||
: authenticator.authenticate(proxy, request.url(), challenges);
|
||||
if (credential == null) return null; // Couldn't satisfy the challenge so end the request cycle.
|
||||
|
||||
// Add authorization credentials, bypassing the already-connected check.
|
||||
return requestHeaders.newBuilder().set(requestField, credential.getHeaderValue()).build();
|
||||
return request.newBuilder().header(requestField, credential.getHeaderValue()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Date;
|
||||
@@ -139,22 +138,18 @@ public class HttpEngine {
|
||||
private boolean connectionReleased;
|
||||
|
||||
/**
|
||||
* @param request the HTTP request without a body. The body must be
|
||||
* written via the engine's request body stream.
|
||||
* @param connection the connection used for an intermediate response
|
||||
* immediately prior to this request/response pair, such as a same-host
|
||||
* redirect. This engine assumes ownership of the connection and must
|
||||
* release it when it is unneeded.
|
||||
*/
|
||||
public HttpEngine(OkHttpClient client, Policy policy, String method, RawHeaders requestHeaders,
|
||||
public HttpEngine(OkHttpClient client, Policy policy, Request request,
|
||||
Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
|
||||
this.client = client;
|
||||
this.policy = policy;
|
||||
|
||||
// This doesn't have a body. When we're sending requests to the cache, we don't need it.
|
||||
this.request = new Request.Builder(policy.getURL())
|
||||
.method(method, null)
|
||||
.rawHeaders(requestHeaders)
|
||||
.build();
|
||||
|
||||
this.request = request;
|
||||
this.connection = connection;
|
||||
this.requestBodyOut = requestBodyOut;
|
||||
}
|
||||
@@ -186,11 +181,8 @@ public class HttpEngine {
|
||||
}
|
||||
this.responseSource = ResponseSource.CACHE;
|
||||
|
||||
RawHeaders gatewayTimeoutHeaders = new RawHeaders.Builder()
|
||||
.setStatusLine("HTTP/1.1 504 Gateway Timeout")
|
||||
.build();
|
||||
this.validatingResponse = new Response.Builder(request, 504)
|
||||
.rawHeaders(gatewayTimeoutHeaders)
|
||||
this.validatingResponse = new Response.Builder(request)
|
||||
.statusLine(new StatusLine("HTTP/1.1 504 Gateway Timeout"))
|
||||
.body(EMPTY_BODY)
|
||||
.build();
|
||||
promoteValidatingResponse();
|
||||
@@ -218,7 +210,8 @@ public class HttpEngine {
|
||||
Response candidate = responseCache.get(request);
|
||||
if (candidate == null) return;
|
||||
|
||||
if (!acceptCacheResponseType(candidate)) {
|
||||
// Drop the cached response if it's missing a required handshake.
|
||||
if (request.isHttps() && candidate.handshake() == null) {
|
||||
Util.closeQuietly(candidate.body());
|
||||
return;
|
||||
}
|
||||
@@ -296,10 +289,6 @@ public class HttpEngine {
|
||||
connection.updateReadTimeout(client.getReadTimeout());
|
||||
}
|
||||
connected(connection);
|
||||
if (connection.getRoute().getProxy() != client.getProxy()) {
|
||||
// Update the request line if the proxy changed; it may need a host name.
|
||||
request = request.newBuilder().setRequestLine(getRequestLine()).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,7 +353,7 @@ public class HttpEngine {
|
||||
if (response == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return response.getHeaders().getResponseCode();
|
||||
return response.code();
|
||||
}
|
||||
|
||||
public final InputStream getResponseBody() {
|
||||
@@ -378,14 +367,6 @@ public class HttpEngine {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if {@code response} is of the right type. This condition is
|
||||
* necessary but not sufficient for the cached response to be used.
|
||||
*/
|
||||
protected boolean acceptCacheResponseType(Response response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void maybeCache() throws IOException {
|
||||
// Are we caching at all?
|
||||
if (!policy.getUseCaches()) return;
|
||||
@@ -468,7 +449,7 @@ public class HttpEngine {
|
||||
* See RFC 2616 section 4.3.
|
||||
*/
|
||||
public final boolean hasResponseBody() {
|
||||
int responseCode = response.getHeaders().getResponseCode();
|
||||
int responseCode = response.code();
|
||||
|
||||
// HEAD requests never yield a body regardless of the response headers.
|
||||
if (request.method().equals("HEAD")) {
|
||||
@@ -500,8 +481,6 @@ public class HttpEngine {
|
||||
private void prepareRawRequestHeaders() throws IOException {
|
||||
Request.Builder result = request.newBuilder();
|
||||
|
||||
result.setRequestLine(getRequestLine());
|
||||
|
||||
if (request.getUserAgent() == null) {
|
||||
result.setUserAgent(getDefaultUserAgent());
|
||||
}
|
||||
@@ -531,63 +510,12 @@ public class HttpEngine {
|
||||
|
||||
CookieHandler cookieHandler = client.getCookieHandler();
|
||||
if (cookieHandler != null) {
|
||||
result.addCookies(cookieHandler.get(
|
||||
request.uri(), request.getHeaders().toMultimap(false)));
|
||||
result.addCookies(cookieHandler.get(request.uri(), request.getHeaders().toMultimap(null)));
|
||||
}
|
||||
|
||||
request = result.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request status line, like "GET / HTTP/1.1". This is exposed
|
||||
* to the application by {@link HttpURLConnectionImpl#getHeaderFields}, so
|
||||
* it needs to be set even if the transport is SPDY.
|
||||
*/
|
||||
String getRequestLine() {
|
||||
String protocol =
|
||||
(connection == null || connection.getHttpMinorVersion() != 0) ? "HTTP/1.1" : "HTTP/1.0";
|
||||
return request.method() + " " + requestString() + " " + protocol;
|
||||
}
|
||||
|
||||
private String requestString() {
|
||||
URL url = request.url();
|
||||
if (includeAuthorityInRequestLine()) {
|
||||
return url.toString();
|
||||
} else {
|
||||
return requestPath(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never
|
||||
* empty, even if the request URL is. Includes the query component if it
|
||||
* exists.
|
||||
*/
|
||||
public static String requestPath(URL url) {
|
||||
String fileOnly = url.getFile();
|
||||
if (fileOnly == null) {
|
||||
return "/";
|
||||
} else if (!fileOnly.startsWith("/")) {
|
||||
return "/" + fileOnly;
|
||||
} else {
|
||||
return fileOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request line should contain the full URL with host
|
||||
* and port (like "GET http://android.com/foo HTTP/1.1") or only the path
|
||||
* (like "GET /foo HTTP/1.1").
|
||||
*
|
||||
* <p>This is non-final because for HTTPS it's never necessary to supply the
|
||||
* full URL, even if a proxy is in use.
|
||||
*/
|
||||
protected boolean includeAuthorityInRequestLine() {
|
||||
return connection == null
|
||||
? policy.usingProxy() // A proxy was requested.
|
||||
: connection.getRoute().getProxy().type() == Proxy.Type.HTTP; // A proxy was selected.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the TLS handshake created when this engine connected, or null if
|
||||
* no TLS connection was made.
|
||||
@@ -680,14 +608,21 @@ public class HttpEngine {
|
||||
initContentStream(transport.getTransferStream(cacheRequest));
|
||||
}
|
||||
|
||||
protected TunnelRequest getTunnelConfig() {
|
||||
return null;
|
||||
private TunnelRequest getTunnelConfig() {
|
||||
if (!request.isHttps()) return null;
|
||||
|
||||
String userAgent = request.getUserAgent();
|
||||
if (userAgent == null) userAgent = getDefaultUserAgent();
|
||||
|
||||
URL url = request.url();
|
||||
return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent,
|
||||
request.getProxyAuthorization());
|
||||
}
|
||||
|
||||
public void receiveHeaders(RawHeaders headers) throws IOException {
|
||||
CookieHandler cookieHandler = client.getCookieHandler();
|
||||
if (cookieHandler != null) {
|
||||
cookieHandler.put(request.uri(), headers.toMultimap(true));
|
||||
cookieHandler.put(request.uri(), headers.toMultimap(null));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,18 +135,38 @@ public final class HttpTransport implements Transport {
|
||||
public void writeRequestHeaders() throws IOException {
|
||||
httpEngine.writingRequestHeaders();
|
||||
RawHeaders headersToSend = httpEngine.getRequest().getHeaders();
|
||||
byte[] bytes = headersToSend.toBytes();
|
||||
String requestLine = RequestLine.get(httpEngine.getRequest(),
|
||||
httpEngine.connection.getRoute().getProxy().type(),
|
||||
httpEngine.connection.getHttpMinorVersion());
|
||||
byte[] bytes = headersToSend.toBytes(requestLine);
|
||||
requestOut.write(bytes);
|
||||
}
|
||||
|
||||
@Override public Response readResponseHeaders() throws IOException {
|
||||
RawHeaders rawHeaders = RawHeaders.readHttpHeaders(socketIn);
|
||||
httpEngine.connection.setHttpMinorVersion(rawHeaders.getHttpMinorVersion());
|
||||
httpEngine.receiveHeaders(rawHeaders);
|
||||
return new Response.Builder(httpEngine.getRequest(), rawHeaders.getResponseCode())
|
||||
Response response = readResponse(httpEngine.getRequest(), socketIn)
|
||||
.handshake(httpEngine.connection.getHandshake())
|
||||
.rawHeaders(rawHeaders)
|
||||
.build();
|
||||
httpEngine.connection.setHttpMinorVersion(response.httpMinorVersion());
|
||||
httpEngine.receiveHeaders(response.rawHeaders());
|
||||
return response;
|
||||
}
|
||||
|
||||
/** Parses bytes of a response header from an HTTP transport. */
|
||||
public static Response.Builder readResponse(Request request, InputStream in) throws IOException {
|
||||
while (true) {
|
||||
String statusLineString = Util.readAsciiLine(in);
|
||||
StatusLine statusLine = new StatusLine(statusLineString);
|
||||
|
||||
Response.Builder responseBuilder = new Response.Builder(request);
|
||||
responseBuilder.statusLine(statusLine);
|
||||
responseBuilder.header(Response.SELECTED_TRANSPORT, "http/1.1");
|
||||
|
||||
RawHeaders.Builder headersBuilder = new RawHeaders.Builder();
|
||||
headersBuilder.readHeaders(in);
|
||||
responseBuilder.rawHeaders(headersBuilder.build());
|
||||
|
||||
if (statusLine.code() != HttpEngine.HTTP_CONTINUE) return responseBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||
|
||||
@@ -19,6 +19,8 @@ package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.Connection;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -142,8 +144,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
*/
|
||||
@Override public final String getHeaderField(String fieldName) {
|
||||
try {
|
||||
RawHeaders rawHeaders = getResponse().getResponse().getHeaders();
|
||||
return fieldName == null ? rawHeaders.getStatusLine() : rawHeaders.get(fieldName);
|
||||
Response response = getResponse().getResponse();
|
||||
return fieldName == null ? response.statusLine() : response.getHeaders().get(fieldName);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -159,7 +161,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
|
||||
@Override public final Map<String, List<String>> getHeaderFields() {
|
||||
try {
|
||||
return getResponse().getResponse().getHeaders().toMultimap(true);
|
||||
Response response = getResponse().getResponse();
|
||||
return response.getHeaders().toMultimap(response.statusLine());
|
||||
} catch (IOException e) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
@@ -170,7 +173,11 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
throw new IllegalStateException(
|
||||
"Cannot access request header fields after connection is set");
|
||||
}
|
||||
return requestHeaders.build().toMultimap(false);
|
||||
|
||||
// For the request line property assigned to the null key, just use no proxy and HTTP 1.1.
|
||||
Request request = new Request.Builder(getURL()).method(method, null).build();
|
||||
String requestLine = RequestLine.get(request, null, 1);
|
||||
return requestHeaders.build().toMultimap(requestLine);
|
||||
}
|
||||
|
||||
@Override public final InputStream getInputStream() throws IOException {
|
||||
@@ -267,13 +274,11 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
|
||||
private HttpEngine newHttpEngine(String method, Connection connection,
|
||||
RetryableOutputStream requestBody) throws IOException {
|
||||
if (url.getProtocol().equals("http")) {
|
||||
return new HttpEngine(client, this, method, requestHeaders.build(), connection, requestBody);
|
||||
} else if (url.getProtocol().equals("https")) {
|
||||
return new HttpsEngine(client, this, method, requestHeaders.build(), connection, requestBody);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
Request request = new Request.Builder(getURL())
|
||||
.method(method, null) // No body: that's provided later!
|
||||
.rawHeaders(requestHeaders.build())
|
||||
.build();
|
||||
return new HttpEngine(client, this, request, connection, requestBody);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,10 +395,6 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
return !sslFailure && !protocolFailure;
|
||||
}
|
||||
|
||||
public HttpEngine getHttpEngine() {
|
||||
return httpEngine;
|
||||
}
|
||||
|
||||
enum Retry {
|
||||
NONE,
|
||||
SAME_CONNECTION,
|
||||
@@ -417,12 +418,10 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
}
|
||||
// fall-through
|
||||
case HTTP_UNAUTHORIZED:
|
||||
RawHeaders successorRequestHeaders = HttpAuthenticator.processAuthHeader(
|
||||
client.getAuthenticator(), getResponseCode(),
|
||||
httpEngine.getResponse().getHeaders(), requestHeaders.build(), selectedProxy,
|
||||
url);
|
||||
if (successorRequestHeaders == null) return Retry.NONE;
|
||||
requestHeaders = successorRequestHeaders.newBuilder();
|
||||
Request successorRequest = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
|
||||
httpEngine.getResponse(), selectedProxy);
|
||||
if (successorRequest == null) return Retry.NONE;
|
||||
requestHeaders = successorRequest.getHeaders().newBuilder();
|
||||
return Retry.SAME_CONNECTION;
|
||||
|
||||
case HTTP_MULT_CHOICE:
|
||||
@@ -494,7 +493,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection implements Policy {
|
||||
}
|
||||
|
||||
@Override public String getResponseMessage() throws IOException {
|
||||
return getResponse().getResponse().getHeaders().getResponseMessage();
|
||||
return getResponse().getResponse().statusMessage();
|
||||
}
|
||||
|
||||
@Override public final int getResponseCode() throws IOException {
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.Connection;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.TunnelRequest;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.getEffectivePort;
|
||||
|
||||
public final class HttpsEngine extends HttpEngine {
|
||||
public HttpsEngine(OkHttpClient client, Policy policy, String method, RawHeaders requestHeaders,
|
||||
Connection connection, RetryableOutputStream requestBody) throws IOException {
|
||||
super(client, policy, method, requestHeaders, connection, requestBody);
|
||||
}
|
||||
|
||||
@Override protected boolean acceptCacheResponseType(Response response) {
|
||||
return response.handshake() != null;
|
||||
}
|
||||
|
||||
@Override protected boolean includeAuthorityInRequestLine() {
|
||||
// Even if there is a proxy, it isn't involved. Always request just the path.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override protected TunnelRequest getTunnelConfig() {
|
||||
String userAgent = getRequest().getUserAgent();
|
||||
if (userAgent == null) {
|
||||
userAgent = getDefaultUserAgent();
|
||||
}
|
||||
|
||||
URL url = getRequest().url();
|
||||
return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent,
|
||||
getRequest().getProxyAuthorization());
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,11 @@
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
|
||||
public interface Policy {
|
||||
/** Returns true if HTTP response caches should be used. */
|
||||
boolean getUseCaches();
|
||||
|
||||
/** Returns the current destination URL, possibly a redirect. */
|
||||
URL getURL();
|
||||
|
||||
/** Returns the If-Modified-Since timestamp, or 0 if none is set. */
|
||||
long getIfModifiedSince();
|
||||
|
||||
|
||||
@@ -17,12 +17,10 @@
|
||||
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -67,41 +65,9 @@ public final class RawHeaders {
|
||||
};
|
||||
|
||||
private final List<String> namesAndValues;
|
||||
private final String requestLine;
|
||||
private final String statusLine;
|
||||
private final int httpMinorVersion;
|
||||
private final int responseCode;
|
||||
private final String responseMessage;
|
||||
|
||||
private RawHeaders(Builder builder) {
|
||||
this.namesAndValues = Util.immutableList(builder.namesAndValues);
|
||||
this.requestLine = builder.requestLine;
|
||||
this.statusLine = builder.statusLine;
|
||||
this.httpMinorVersion = builder.httpMinorVersion;
|
||||
this.responseCode = builder.responseCode;
|
||||
this.responseMessage = builder.responseMessage;
|
||||
}
|
||||
|
||||
public String getStatusLine() {
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0
|
||||
* and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown.
|
||||
*/
|
||||
public int getHttpMinorVersion() {
|
||||
return httpMinorVersion != -1 ? httpMinorVersion : 1;
|
||||
}
|
||||
|
||||
/** Returns the HTTP status code or -1 if it is unknown. */
|
||||
public int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
/** Returns the HTTP status message or null if it is unknown. */
|
||||
public String getResponseMessage() {
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
/** Returns the number of field values. */
|
||||
@@ -168,7 +134,7 @@ public final class RawHeaders {
|
||||
}
|
||||
|
||||
/** Returns bytes of a request header for sending on an HTTP transport. */
|
||||
public byte[] toBytes() throws UnsupportedEncodingException {
|
||||
public byte[] toBytes(String requestLine) throws UnsupportedEncodingException {
|
||||
StringBuilder result = new StringBuilder(256);
|
||||
result.append(requestLine).append("\r\n");
|
||||
for (int i = 0; i < namesAndValues.size(); i += 2) {
|
||||
@@ -181,23 +147,13 @@ public final class RawHeaders {
|
||||
return result.toString().getBytes("ISO-8859-1");
|
||||
}
|
||||
|
||||
/** Parses bytes of a response header from an HTTP transport. */
|
||||
public static RawHeaders readHttpHeaders(InputStream in) throws IOException {
|
||||
Builder builder;
|
||||
do {
|
||||
builder = new Builder();
|
||||
builder.set(Response.SELECTED_TRANSPORT, "http/1.1");
|
||||
builder.setStatusLine(Util.readAsciiLine(in));
|
||||
builder.readHeaders(in);
|
||||
} while (builder.responseCode == HttpEngine.HTTP_CONTINUE);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable map containing each field to its list of values. The
|
||||
* status line is mapped to null.
|
||||
* Returns an immutable map containing each field to its list of values.
|
||||
*
|
||||
* @param valueForNullKey the request line for requests, or the status line
|
||||
* for responses. If non-null, this value is mapped to the null key.
|
||||
*/
|
||||
public Map<String, List<String>> toMultimap(boolean response) {
|
||||
public Map<String, List<String>> toMultimap(String valueForNullKey) {
|
||||
Map<String, List<String>> result = new TreeMap<String, List<String>>(FIELD_NAME_COMPARATOR);
|
||||
for (int i = 0; i < namesAndValues.size(); i += 2) {
|
||||
String fieldName = namesAndValues.get(i);
|
||||
@@ -211,10 +167,8 @@ public final class RawHeaders {
|
||||
allValues.add(value);
|
||||
result.put(fieldName, Collections.unmodifiableList(allValues));
|
||||
}
|
||||
if (response && statusLine != null) {
|
||||
result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine)));
|
||||
} else if (requestLine != null) {
|
||||
result.put(null, Collections.unmodifiableList(Collections.singletonList(requestLine)));
|
||||
if (valueForNullKey != null) {
|
||||
result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
@@ -258,49 +212,9 @@ public final class RawHeaders {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns headers for a name value block containing a SPDY response. */
|
||||
public static RawHeaders fromNameValueBlock(List<String> nameValueBlock) throws IOException {
|
||||
if (nameValueBlock.size() % 2 != 0) {
|
||||
throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock);
|
||||
}
|
||||
String status = null;
|
||||
String version = null;
|
||||
Builder builder = new Builder();
|
||||
builder.set(Response.SELECTED_TRANSPORT, "spdy/3");
|
||||
for (int i = 0; i < nameValueBlock.size(); i += 2) {
|
||||
String name = nameValueBlock.get(i);
|
||||
String values = nameValueBlock.get(i + 1);
|
||||
for (int start = 0; start < values.length(); ) {
|
||||
int end = values.indexOf('\0', start);
|
||||
if (end == -1) {
|
||||
end = values.length();
|
||||
}
|
||||
String value = values.substring(start, end);
|
||||
if (":status".equals(name)) {
|
||||
status = value;
|
||||
} else if (":version".equals(name)) {
|
||||
version = value;
|
||||
} else {
|
||||
builder.namesAndValues.add(name);
|
||||
builder.namesAndValues.add(value);
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
if (status == null) throw new ProtocolException("Expected ':status' header not present");
|
||||
if (version == null) throw new ProtocolException("Expected ':version' header not present");
|
||||
builder.setStatusLine(version + " " + status);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public Builder newBuilder() {
|
||||
Builder result = new Builder();
|
||||
result.namesAndValues.addAll(namesAndValues);
|
||||
result.requestLine = requestLine;
|
||||
result.statusLine = statusLine;
|
||||
result.httpMinorVersion = httpMinorVersion;
|
||||
result.responseCode = responseCode;
|
||||
result.responseMessage = responseMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -315,61 +229,12 @@ public final class RawHeaders {
|
||||
|
||||
public static class Builder {
|
||||
private final List<String> namesAndValues = new ArrayList<String>(20);
|
||||
private String requestLine;
|
||||
private String statusLine;
|
||||
private int httpMinorVersion = 1;
|
||||
private int responseCode = -1;
|
||||
private String responseMessage;
|
||||
|
||||
/** Sets the request line (like "GET / HTTP/1.1"). */
|
||||
public Builder setRequestLine(String requestLine) {
|
||||
this.requestLine = requestLine.trim();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Equivalent to {@code build().get(fieldName)}, but potentially faster. */
|
||||
public String get(String fieldName) {
|
||||
return RawHeaders.get(namesAndValues, fieldName);
|
||||
}
|
||||
|
||||
/** Equivalent to {@code build().getResponseCode()}, but potentially faster. */
|
||||
public int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
/** Sets the response status line (like "HTTP/1.0 200 OK"). */
|
||||
public Builder setStatusLine(String statusLine) throws IOException {
|
||||
// H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
||||
if (this.responseMessage != null) {
|
||||
throw new IllegalStateException("statusLine is already set");
|
||||
}
|
||||
// We allow empty message without leading white space since some servers
|
||||
// do not send the white space when the message is empty.
|
||||
boolean hasMessage = statusLine.length() > 13;
|
||||
if (!statusLine.startsWith("HTTP/1.")
|
||||
|| statusLine.length() < 12
|
||||
|| statusLine.charAt(8) != ' '
|
||||
|| (hasMessage && statusLine.charAt(12) != ' ')) {
|
||||
throw new ProtocolException("Unexpected status line: " + statusLine);
|
||||
}
|
||||
int httpMinorVersion = statusLine.charAt(7) - '0';
|
||||
if (httpMinorVersion < 0 || httpMinorVersion > 9) {
|
||||
throw new ProtocolException("Unexpected status line: " + statusLine);
|
||||
}
|
||||
int responseCode;
|
||||
try {
|
||||
responseCode = Integer.parseInt(statusLine.substring(9, 12));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ProtocolException("Unexpected status line: " + statusLine);
|
||||
}
|
||||
this.responseMessage = hasMessage ? statusLine.substring(13) : "";
|
||||
this.responseCode = responseCode;
|
||||
this.statusLine = statusLine;
|
||||
this.httpMinorVersion = httpMinorVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an HTTP header line containing a field name, a literal colon, and a
|
||||
* value. This works around empty header names and header names that start
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.Request;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
|
||||
public final class RequestLine {
|
||||
private RequestLine() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the request status line, like "GET / HTTP/1.1". This is exposed
|
||||
* to the application by {@link HttpURLConnectionImpl#getHeaderFields}, so
|
||||
* it needs to be set even if the transport is SPDY.
|
||||
*/
|
||||
static String get(Request request, Proxy.Type proxyType, int httpMinorVersion) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(request.method());
|
||||
result.append(" ");
|
||||
|
||||
if (includeAuthorityInRequestLine(request, proxyType)) {
|
||||
result.append(request.url());
|
||||
} else {
|
||||
result.append(requestPath(request.url()));
|
||||
}
|
||||
|
||||
result.append(" ");
|
||||
result.append(version(httpMinorVersion));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request line should contain the full URL with host
|
||||
* and port (like "GET http://android.com/foo HTTP/1.1") or only the path
|
||||
* (like "GET /foo HTTP/1.1").
|
||||
*/
|
||||
private static boolean includeAuthorityInRequestLine(Request request, Proxy.Type proxyType) {
|
||||
return !request.isHttps() && proxyType == Proxy.Type.HTTP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to request, like the '/' in 'GET / HTTP/1.1'. Never empty,
|
||||
* even if the request URL is. Includes the query component if it exists.
|
||||
*/
|
||||
public static String requestPath(URL url) {
|
||||
String pathAndQuery = url.getFile();
|
||||
if (pathAndQuery == null) return "/";
|
||||
if (!pathAndQuery.startsWith("/")) return "/" + pathAndQuery;
|
||||
return pathAndQuery;
|
||||
}
|
||||
|
||||
public static String version(int httpMinorVersion) {
|
||||
return httpMinorVersion == 1 ? "HTTP/1.1" : "HTTP/1.0";
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
@@ -41,9 +42,9 @@ public final class SpdyTransport implements Transport {
|
||||
@Override public Request prepareRequest(Request request) {
|
||||
Request.Builder builder = request.newBuilder();
|
||||
|
||||
String version = httpEngine.connection.getHttpMinorVersion() == 1 ? "HTTP/1.1" : "HTTP/1.0";
|
||||
String version = RequestLine.version(httpEngine.connection.getHttpMinorVersion());
|
||||
URL url = request.url();
|
||||
builder.addSpdyRequestHeaders(request.method(), HttpEngine.requestPath(url), version,
|
||||
builder.addSpdyRequestHeaders(request.method(), RequestLine.requestPath(url), version,
|
||||
HttpEngine.getOriginAddress(url), httpEngine.getRequest().url().getProtocol());
|
||||
|
||||
if (httpEngine.hasRequestBody()) {
|
||||
@@ -83,12 +84,50 @@ public final class SpdyTransport implements Transport {
|
||||
|
||||
@Override public Response readResponseHeaders() throws IOException {
|
||||
List<String> nameValueBlock = stream.getResponseHeaders();
|
||||
RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
|
||||
httpEngine.receiveHeaders(rawHeaders);
|
||||
return new Response.Builder(httpEngine.getRequest(), rawHeaders.getResponseCode())
|
||||
Response response = readNameValueBlock(httpEngine.getRequest(), nameValueBlock)
|
||||
.handshake(httpEngine.connection.getHandshake())
|
||||
.rawHeaders(rawHeaders)
|
||||
.build();
|
||||
httpEngine.connection.setHttpMinorVersion(response.httpMinorVersion());
|
||||
httpEngine.receiveHeaders(response.rawHeaders());
|
||||
return response;
|
||||
}
|
||||
|
||||
/** Returns headers for a name value block containing a SPDY response. */
|
||||
public static Response.Builder readNameValueBlock(Request request, List<String> nameValueBlock)
|
||||
throws IOException {
|
||||
if (nameValueBlock.size() % 2 != 0) {
|
||||
throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock);
|
||||
}
|
||||
String status = null;
|
||||
String version = null;
|
||||
|
||||
RawHeaders.Builder headersBuilder = new RawHeaders.Builder();
|
||||
headersBuilder.set(Response.SELECTED_TRANSPORT, "spdy/3");
|
||||
for (int i = 0; i < nameValueBlock.size(); i += 2) {
|
||||
String name = nameValueBlock.get(i);
|
||||
String values = nameValueBlock.get(i + 1);
|
||||
for (int start = 0; start < values.length(); ) {
|
||||
int end = values.indexOf('\0', start);
|
||||
if (end == -1) {
|
||||
end = values.length();
|
||||
}
|
||||
String value = values.substring(start, end);
|
||||
if (":status".equals(name)) {
|
||||
status = value;
|
||||
} else if (":version".equals(name)) {
|
||||
version = value;
|
||||
} else {
|
||||
headersBuilder.add(name, value);
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
if (status == null) throw new ProtocolException("Expected ':status' header not present");
|
||||
if (version == null) throw new ProtocolException("Expected ':version' header not present");
|
||||
|
||||
return new Response.Builder(request)
|
||||
.statusLine(new StatusLine(version + " " + status))
|
||||
.rawHeaders(headersBuilder.build());
|
||||
}
|
||||
|
||||
@Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
|
||||
public final class StatusLine {
|
||||
private final String statusLine;
|
||||
private final int httpMinorVersion;
|
||||
private final int responseCode;
|
||||
private final String responseMessage;
|
||||
|
||||
/** Sets the response status line (like "HTTP/1.0 200 OK"). */
|
||||
public StatusLine(String statusLine) throws IOException {
|
||||
// H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
|
||||
|
||||
// We allow empty message without leading white space since some servers
|
||||
// do not send the white space when the message is empty.
|
||||
boolean hasMessage = statusLine.length() > 13;
|
||||
if (!statusLine.startsWith("HTTP/1.")
|
||||
|| statusLine.length() < 12
|
||||
|| statusLine.charAt(8) != ' '
|
||||
|| (hasMessage && statusLine.charAt(12) != ' ')) {
|
||||
throw new ProtocolException("Unexpected status line: " + statusLine);
|
||||
}
|
||||
int httpMinorVersion = statusLine.charAt(7) - '0';
|
||||
if (httpMinorVersion < 0 || httpMinorVersion > 9) {
|
||||
throw new ProtocolException("Unexpected status line: " + statusLine);
|
||||
}
|
||||
int responseCode;
|
||||
try {
|
||||
responseCode = Integer.parseInt(statusLine.substring(9, 12));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ProtocolException("Unexpected status line: " + statusLine);
|
||||
}
|
||||
this.responseMessage = hasMessage ? statusLine.substring(13) : "";
|
||||
this.responseCode = responseCode;
|
||||
this.statusLine = statusLine;
|
||||
this.httpMinorVersion = httpMinorVersion;
|
||||
}
|
||||
|
||||
public String getStatusLine() {
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0
|
||||
* and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown.
|
||||
*/
|
||||
public int httpMinorVersion() {
|
||||
return httpMinorVersion != -1 ? httpMinorVersion : 1;
|
||||
}
|
||||
|
||||
/** Returns the HTTP status code or -1 if it is unknown. */
|
||||
public int code() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
/** Returns the HTTP status message or null if it is unknown. */
|
||||
public String message() {
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@@ -31,9 +32,11 @@ public final class RawHeadersTest {
|
||||
"set-cookie", "Cookie1\u0000Cookie2",
|
||||
":status", "200 OK",
|
||||
":version", "HTTP/1.1");
|
||||
RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
|
||||
Request request = new Request.Builder("http://square.com/").build();
|
||||
Response response = SpdyTransport.readNameValueBlock(request, nameValueBlock).build();
|
||||
RawHeaders rawHeaders = response.rawHeaders();
|
||||
assertEquals(4, rawHeaders.length());
|
||||
assertEquals("HTTP/1.1 200 OK", rawHeaders.getStatusLine());
|
||||
assertEquals("HTTP/1.1 200 OK", response.statusLine());
|
||||
assertEquals("no-cache, no-store", rawHeaders.get("cache-control"));
|
||||
assertEquals("Cookie2", rawHeaders.get("set-cookie"));
|
||||
assertEquals("spdy/3", rawHeaders.get(Response.SELECTED_TRANSPORT));
|
||||
@@ -55,7 +58,6 @@ public final class RawHeadersTest {
|
||||
builder.add("set-cookie", "Cookie1");
|
||||
builder.add("set-cookie", "Cookie2");
|
||||
builder.add(":status", "200 OK");
|
||||
// TODO: fromNameValueBlock should take the status line headers
|
||||
List<String> nameValueBlock = builder.build().toNameValueBlock();
|
||||
List<String> expected = Arrays.asList(
|
||||
"cache-control", "no-cache, no-store",
|
||||
@@ -70,43 +72,4 @@ public final class RawHeadersTest {
|
||||
builder.add("Transfer-Encoding", "chunked");
|
||||
assertEquals(Arrays.<String>asList(), builder.build().toNameValueBlock());
|
||||
}
|
||||
|
||||
@Test public void statusMessage() throws IOException {
|
||||
RawHeaders.Builder builder = new RawHeaders.Builder();
|
||||
String message = "Temporary Redirect";
|
||||
int version = 1;
|
||||
int code = 200;
|
||||
builder.setStatusLine("HTTP/1." + version + " " + code + " " + message);
|
||||
RawHeaders rawHeaders = builder.build();
|
||||
assertEquals(message, rawHeaders.getResponseMessage());
|
||||
assertEquals(version, rawHeaders.getHttpMinorVersion());
|
||||
assertEquals(code, rawHeaders.getResponseCode());
|
||||
}
|
||||
|
||||
@Test public void statusMessageWithEmptyMessage() throws IOException {
|
||||
RawHeaders.Builder builder = new RawHeaders.Builder();
|
||||
int version = 1;
|
||||
int code = 503;
|
||||
builder.setStatusLine("HTTP/1." + version + " " + code + " ");
|
||||
RawHeaders rawHeaders = builder.build();
|
||||
assertEquals("", rawHeaders.getResponseMessage());
|
||||
assertEquals(version, rawHeaders.getHttpMinorVersion());
|
||||
assertEquals(code, rawHeaders.getResponseCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not defined in the protocol but some servers won't add the leading
|
||||
* empty space when the message is empty.
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
|
||||
*/
|
||||
@Test public void statusMessageWithEmptyMessageAndNoLeadingSpace() throws IOException {
|
||||
RawHeaders.Builder builder = new RawHeaders.Builder();
|
||||
int version = 1;
|
||||
int code = 503;
|
||||
builder.setStatusLine("HTTP/1." + version + " " + code);
|
||||
RawHeaders rawHeaders = builder.build();
|
||||
assertEquals("", rawHeaders.getResponseMessage());
|
||||
assertEquals(version, rawHeaders.getHttpMinorVersion());
|
||||
assertEquals(code, rawHeaders.getResponseCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public final class StatusLineTest {
|
||||
@Test public void parse() throws IOException {
|
||||
String message = "Temporary Redirect";
|
||||
int version = 1;
|
||||
int code = 200;
|
||||
StatusLine statusLine = new StatusLine("HTTP/1." + version + " " + code + " " + message);
|
||||
assertEquals(message, statusLine.message());
|
||||
assertEquals(version, statusLine.httpMinorVersion());
|
||||
assertEquals(code, statusLine.code());
|
||||
}
|
||||
|
||||
@Test public void emptyMessage() throws IOException {
|
||||
int version = 1;
|
||||
int code = 503;
|
||||
StatusLine statusLine = new StatusLine("HTTP/1." + version + " " + code + " ");
|
||||
assertEquals("", statusLine.message());
|
||||
assertEquals(version, statusLine.httpMinorVersion());
|
||||
assertEquals(code, statusLine.code());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not defined in the protocol but some servers won't add the leading
|
||||
* empty space when the message is empty.
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
|
||||
*/
|
||||
@Test public void emptyMessageAndNoLeadingSpace() throws IOException {
|
||||
int version = 1;
|
||||
int code = 503;
|
||||
StatusLine statusLine = new StatusLine("HTTP/1." + version + " " + code);
|
||||
assertEquals("", statusLine.message());
|
||||
assertEquals(version, statusLine.httpMinorVersion());
|
||||
assertEquals(code, statusLine.code());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.HttpResponseCache;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Response;
|
||||
import com.squareup.okhttp.internal.RecordingAuthenticator;
|
||||
import com.squareup.okhttp.internal.RecordingHostnameVerifier;
|
||||
import com.squareup.okhttp.internal.RecordingOkAuthenticator;
|
||||
@@ -214,14 +213,12 @@ public final class URLConnectionTest {
|
||||
fail("Modified an unmodifiable view.");
|
||||
} catch (UnsupportedOperationException expected) {
|
||||
}
|
||||
assertEquals(Response.SELECTED_TRANSPORT, urlConnection.getHeaderFieldKey(0));
|
||||
assertEquals("http/1.1", urlConnection.getHeaderField(0));
|
||||
assertEquals("A", urlConnection.getHeaderFieldKey(1));
|
||||
assertEquals("c", urlConnection.getHeaderField(1));
|
||||
assertEquals("B", urlConnection.getHeaderFieldKey(2));
|
||||
assertEquals("d", urlConnection.getHeaderField(2));
|
||||
assertEquals("A", urlConnection.getHeaderFieldKey(3));
|
||||
assertEquals("e", urlConnection.getHeaderField(3));
|
||||
assertEquals("A", urlConnection.getHeaderFieldKey(0));
|
||||
assertEquals("c", urlConnection.getHeaderField(0));
|
||||
assertEquals("B", urlConnection.getHeaderFieldKey(1));
|
||||
assertEquals("d", urlConnection.getHeaderField(1));
|
||||
assertEquals("A", urlConnection.getHeaderFieldKey(2));
|
||||
assertEquals("e", urlConnection.getHeaderField(2));
|
||||
}
|
||||
|
||||
@Test public void serverSendsInvalidResponseHeaders() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user