1
0
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:
Jesse Wilson
2013-12-30 22:08:23 -08:00
20 changed files with 399 additions and 451 deletions

View File

@@ -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 {

View File

@@ -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());
}
}
}

View File

@@ -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)

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
/**

View File

@@ -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));
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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());
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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";
}
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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 {