mirror of
https://github.com/square/okhttp.git
synced 2026-01-21 03:41:07 +03:00
Move request line and status line out of RawHeaders.
The request line is now standalone because it depends on the proxy (for full URL or not) and the connection (for HTTP version). I may change this later and put this info in the request. The status line now moves into Response. I have a helper class StatusLine that does the parsing. I may later want to hide this or make it public; I'm not quite sure yet! The net result is that RawHeaders is getting dumber and used in fewer places, which is good news. This change also shows nice simplifications to the authenticator (which needs fewer args) and to the tunnel builder. Also fold together HttpsEngine and HttpEngine; the HTTPS subclass wasn't pulling its weight.
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