mirror of
https://github.com/square/okhttp.git
synced 2026-01-25 16:01:38 +03:00
Merge pull request #225 from square/jwilson/experimental
First step in an async HTTP API.
This commit is contained in:
138
okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java
Normal file
138
okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class Dispatcher {
|
||||
// TODO: thread pool size should be configurable; possibly configurable per host.
|
||||
private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
|
||||
8, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
private final Map<Object, List<Job>> enqueuedJobs = new LinkedHashMap<Object, List<Job>>();
|
||||
|
||||
public synchronized void enqueue(
|
||||
HttpURLConnection connection, Request request, Response.Receiver responseReceiver) {
|
||||
Job job = new Job(connection, request, responseReceiver);
|
||||
List<Job> jobsForTag = enqueuedJobs.get(request.tag());
|
||||
if (jobsForTag == null) {
|
||||
jobsForTag = new ArrayList<Job>(2);
|
||||
enqueuedJobs.put(request.tag(), jobsForTag);
|
||||
}
|
||||
jobsForTag.add(job);
|
||||
executorService.execute(job);
|
||||
}
|
||||
|
||||
public synchronized void cancel(Object tag) {
|
||||
List<Job> jobs = enqueuedJobs.remove(tag);
|
||||
if (jobs == null) return;
|
||||
for (Job job : jobs) {
|
||||
executorService.remove(job);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void finished(Job job) {
|
||||
List<Job> jobs = enqueuedJobs.get(job.request.tag());
|
||||
if (jobs != null) jobs.remove(job);
|
||||
}
|
||||
|
||||
public class Job implements Runnable {
|
||||
private final HttpURLConnection connection;
|
||||
private final Request request;
|
||||
private final Response.Receiver responseReceiver;
|
||||
|
||||
public Job(HttpURLConnection connection, Request request, Response.Receiver responseReceiver) {
|
||||
this.connection = connection;
|
||||
this.request = request;
|
||||
this.responseReceiver = responseReceiver;
|
||||
}
|
||||
|
||||
@Override public void run() {
|
||||
try {
|
||||
sendRequest();
|
||||
Response response = readResponse();
|
||||
responseReceiver.onResponse(response);
|
||||
} catch (IOException e) {
|
||||
responseReceiver.onFailure(new Failure.Builder()
|
||||
.request(request)
|
||||
.exception(e)
|
||||
.build());
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
finished(this);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection sendRequest()
|
||||
throws IOException {
|
||||
for (int i = 0; i < request.headerCount(); i++) {
|
||||
connection.addRequestProperty(request.headerName(i), request.headerValue(i));
|
||||
}
|
||||
Request.Body body = request.body();
|
||||
if (body != null) {
|
||||
connection.setDoOutput(true);
|
||||
body.writeTo(connection.getOutputStream());
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private Response readResponse() throws IOException {
|
||||
int responseCode = connection.getResponseCode();
|
||||
Response.Builder responseBuilder = new Response.Builder(request, responseCode);
|
||||
|
||||
for (int i = 0; true; i++) {
|
||||
String name = connection.getHeaderFieldKey(i);
|
||||
if (name == null) break;
|
||||
String value = connection.getHeaderField(i);
|
||||
responseBuilder.addHeader(name, value);
|
||||
}
|
||||
|
||||
responseBuilder.body(new RealResponseBody(connection, connection.getInputStream()));
|
||||
// TODO: set redirectedBy
|
||||
return responseBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
static class RealResponseBody extends Response.Body {
|
||||
private final HttpURLConnection connection;
|
||||
private final InputStream in;
|
||||
|
||||
RealResponseBody(HttpURLConnection connection, InputStream in) {
|
||||
this.connection = connection;
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override public String contentType() {
|
||||
return connection.getHeaderField("Content-Type");
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return connection.getContentLength(); // TODO: getContentLengthLong
|
||||
}
|
||||
|
||||
@Override public InputStream byteStream() throws IOException {
|
||||
return in;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
okhttp/src/main/java/com/squareup/okhttp/Failure.java
Normal file
59
okhttp/src/main/java/com/squareup/okhttp/Failure.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
/**
|
||||
* A failure attempting to retrieve an HTTP response.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
public class Failure {
|
||||
private final Request request;
|
||||
private final Throwable exception;
|
||||
|
||||
private Failure(Builder builder) {
|
||||
this.request = builder.request;
|
||||
this.exception = builder.exception;
|
||||
}
|
||||
|
||||
public Request request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public Throwable exception() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Request request;
|
||||
private Throwable exception;
|
||||
|
||||
public Builder request(Request request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder exception(Throwable exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Failure build() {
|
||||
return new Failure(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
|
||||
private boolean followProtocolRedirects = true;
|
||||
private int connectTimeout;
|
||||
private int readTimeout;
|
||||
private Dispatcher dispatcher = new Dispatcher();
|
||||
|
||||
public OkHttpClient() {
|
||||
this.failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
|
||||
@@ -311,6 +312,24 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
|
||||
return transports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules {@code request} to be executed.
|
||||
*/
|
||||
public void enqueue(Request request, Response.Receiver responseReceiver) {
|
||||
// Create the HttpURLConnection immediately so the enqueued job gets the current settings of
|
||||
// this client. Otherwise changes to this client (socket factory, redirect policy, etc.) may
|
||||
// incorrectly be reflected in the request when it is dispatched later.
|
||||
dispatcher.enqueue(open(request.url()), request, responseReceiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all scheduled tasks tagged with {@code tag}. Requests that are already
|
||||
* in flight might not be canceled.
|
||||
*/
|
||||
public void cancel(Object tag) {
|
||||
dispatcher.cancel(tag);
|
||||
}
|
||||
|
||||
public HttpURLConnection open(URL url) {
|
||||
String protocol = url.getProtocol();
|
||||
OkHttpClient copy = copyWithDefaults();
|
||||
|
||||
193
okhttp/src/main/java/com/squareup/okhttp/Request.java
Normal file
193
okhttp/src/main/java/com/squareup/okhttp/Request.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An HTTP request. Instances of this class are immutable if their {@link #body}
|
||||
* is null or itself immutable.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
public final class Request {
|
||||
private final URL url;
|
||||
private final String method;
|
||||
private final RawHeaders headers;
|
||||
private final Body body;
|
||||
private final Object tag;
|
||||
|
||||
private Request(Builder builder) {
|
||||
this.url = builder.url;
|
||||
this.method = builder.method;
|
||||
this.headers = new RawHeaders(builder.headers);
|
||||
this.body = builder.body;
|
||||
this.tag = builder.tag != null ? builder.tag : this;
|
||||
}
|
||||
|
||||
public URL url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String urlString() {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
public String method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String header(String name) {
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
public List<String> headers(String name) {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
public Set<String> headerNames() {
|
||||
return headers.names();
|
||||
}
|
||||
|
||||
public int headerCount() {
|
||||
return headers.length();
|
||||
}
|
||||
|
||||
public String headerName(int index) {
|
||||
return headers.getFieldName(index);
|
||||
}
|
||||
|
||||
public String headerValue(int index) {
|
||||
return headers.getValue(index);
|
||||
}
|
||||
|
||||
public Body body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Object tag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public abstract class Body {
|
||||
/** Returns the Content-Type header for this body, or null if the content type is unknown. */
|
||||
public String contentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the number of bytes in this body, or -1 if that count is unknown. */
|
||||
public long contentLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public abstract void writeTo(OutputStream out) throws IOException;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private URL url;
|
||||
private String method = "GET";
|
||||
private final RawHeaders headers = new RawHeaders();
|
||||
private Body body;
|
||||
private Object tag;
|
||||
|
||||
public Builder(String url) {
|
||||
url(url);
|
||||
}
|
||||
|
||||
public Builder(URL url) {
|
||||
url(url);
|
||||
}
|
||||
|
||||
public Builder url(String url) {
|
||||
try {
|
||||
this.url = new URL(url);
|
||||
return this;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Malformed URL: " + url);
|
||||
}
|
||||
}
|
||||
|
||||
public Builder url(URL url) {
|
||||
if (url == null) throw new IllegalStateException("url == null");
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the header named {@code name} to {@code value}. If this request
|
||||
* already has any headers with that name, they are all replaced.
|
||||
*/
|
||||
public Builder header(String name, String value) {
|
||||
headers.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header with {@code name} and {@code value}. Prefer this method for
|
||||
* multiply-valued headers like "Cookie".
|
||||
*/
|
||||
public Builder addHeader(String name, String value) {
|
||||
headers.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder get() {
|
||||
return method("GET", null);
|
||||
}
|
||||
|
||||
public Builder head() {
|
||||
return method("HEAD", null);
|
||||
}
|
||||
|
||||
public Builder post(Body body) {
|
||||
return method("POST", body);
|
||||
}
|
||||
|
||||
public Builder put(Body body) {
|
||||
return method("PUT", body);
|
||||
}
|
||||
|
||||
public Builder method(String method, Body body) {
|
||||
if (method == null || method.length() == 0) {
|
||||
throw new IllegalArgumentException("method == null || method.length() == 0");
|
||||
}
|
||||
this.method = method;
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches {@code tag} to the request. It can be used later to cancel the
|
||||
* request. If the tag is unspecified or null, the request is canceled by
|
||||
* using the request itself as the tag.
|
||||
*/
|
||||
public Builder tag(Object tag) {
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Request build() {
|
||||
return new Request(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
217
okhttp/src/main/java/com/squareup/okhttp/Response.java
Normal file
217
okhttp/src/main/java/com/squareup/okhttp/Response.java
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An HTTP response. Instances of this class are not immutable: the response
|
||||
* body is a one-shot value that may be consumed only once. All other properties
|
||||
* are immutable.
|
||||
*
|
||||
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
|
||||
* This class is in beta. APIs are subject to change!
|
||||
*/
|
||||
public final class Response {
|
||||
private final Request request;
|
||||
private final int code;
|
||||
private final RawHeaders headers;
|
||||
private final Body body;
|
||||
private final Response redirectedBy;
|
||||
|
||||
private Response(Builder builder) {
|
||||
this.request = builder.request;
|
||||
this.code = builder.code;
|
||||
this.headers = new RawHeaders(builder.headers);
|
||||
this.body = builder.body;
|
||||
this.redirectedBy = builder.redirectedBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wire-level request that initiated this HTTP response. This is usually
|
||||
* <strong>not</strong> the same request instance provided to the HTTP client:
|
||||
* <ul>
|
||||
* <li>It may be transformed by the HTTP client. For example, the client
|
||||
* may have added its own {@code Content-Encoding} header to enable
|
||||
* response compression.
|
||||
* <li>It may be the request generated in response to an HTTP redirect.
|
||||
* In this case the request URL may be different than the initial
|
||||
* request URL.
|
||||
* </ul>
|
||||
*/
|
||||
public Request request() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String header(String name) {
|
||||
return header(name, null);
|
||||
}
|
||||
|
||||
public String header(String name, String defaultValue) {
|
||||
String result = headers.get(name);
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
|
||||
public List<String> headers(String name) {
|
||||
return headers.values(name);
|
||||
}
|
||||
|
||||
public Set<String> headerNames() {
|
||||
return headers.names();
|
||||
}
|
||||
|
||||
public int headerCount() {
|
||||
return headers.length();
|
||||
}
|
||||
|
||||
public String headerName(int index) {
|
||||
return headers.getFieldName(index);
|
||||
}
|
||||
|
||||
public String headerValue(int index) {
|
||||
return headers.getValue(index);
|
||||
}
|
||||
|
||||
public Body body() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response for the HTTP redirect that triggered this response, or
|
||||
* null if this response wasn't triggered by an automatic redirect. The body
|
||||
* of the returned response should not be read because it has already been
|
||||
* consumed by the redirecting client.
|
||||
*/
|
||||
public Response redirectedBy() {
|
||||
return redirectedBy;
|
||||
}
|
||||
|
||||
public abstract static class Body {
|
||||
public String contentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public long contentLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public abstract InputStream byteStream() throws IOException;
|
||||
|
||||
public byte[] bytes() throws IOException {
|
||||
long contentLength = contentLength();
|
||||
if (contentLength > Integer.MAX_VALUE) {
|
||||
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
|
||||
}
|
||||
|
||||
if (contentLength != -1) {
|
||||
byte[] content = new byte[(int) contentLength];
|
||||
InputStream in = byteStream();
|
||||
Util.readFully(in, content);
|
||||
if (in.read() != -1) throw new IOException("Content-Length and stream length disagree");
|
||||
return content;
|
||||
|
||||
} else {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Util.copy(byteStream(), out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response bytes as a UTF-8 character stream. Do not call this
|
||||
* method if the response content is not a UTF-8 character stream.
|
||||
*/
|
||||
public Reader charStream() throws IOException {
|
||||
// TODO: parse content-type.
|
||||
return new InputStreamReader(byteStream(), "UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response bytes as a UTF-8 string. Do not call this method if
|
||||
* the response content is not a UTF-8 character stream.
|
||||
*/
|
||||
public String string() throws IOException {
|
||||
// TODO: parse content-type.
|
||||
return new String(bytes(), "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
public interface Receiver {
|
||||
void onFailure(Failure failure);
|
||||
void onResponse(Response response) throws IOException;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Request request;
|
||||
private final int code;
|
||||
private final RawHeaders headers = new RawHeaders();
|
||||
private Body body;
|
||||
private Response redirectedBy;
|
||||
|
||||
public Builder(Request request, int code) {
|
||||
if (request == null) throw new IllegalArgumentException("request == null");
|
||||
if (code <= 0) throw new IllegalArgumentException("code <= 0");
|
||||
this.request = request;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the header named {@code name} to {@code value}. If this request
|
||||
* already has any headers with that name, they are all replaced.
|
||||
*/
|
||||
public Builder header(String name, String value) {
|
||||
headers.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header with {@code name} and {@code value}. Prefer this method for
|
||||
* multiply-valued headers like "Set-Cookie".
|
||||
*/
|
||||
public Builder addHeader(String name, String value) {
|
||||
headers.add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder body(Body body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder redirectedBy(Response redirectedBy) {
|
||||
this.redirectedBy = redirectedBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Response build() {
|
||||
if (request == null) throw new IllegalStateException("Response has no request.");
|
||||
if (code == -1) throw new IllegalStateException("Response has no code.");
|
||||
return new Response(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,8 +606,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
||||
// the list contains "http/1.1". We do this in a separate loop
|
||||
// to avoid modifying any state before we validate the input.
|
||||
boolean containsHttp = false;
|
||||
for (int i = 0; i < transports.length; ++i) {
|
||||
if ("http/1.1".equals(transports[i])) {
|
||||
for (String transport : transports) {
|
||||
if ("http/1.1".equals(transport)) {
|
||||
containsHttp = true;
|
||||
break;
|
||||
}
|
||||
@@ -620,13 +620,13 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
|
||||
transportsList.addAll(this.transports);
|
||||
}
|
||||
|
||||
for (int i = 0; i < transports.length; ++i) {
|
||||
if (transports[i].length() == 0) {
|
||||
for (String transport : transports) {
|
||||
if (transport.length() == 0) {
|
||||
throw new IllegalArgumentException("Transport list contains an empty transport");
|
||||
}
|
||||
|
||||
if (!transportsList.contains(transports[i])) {
|
||||
transportsList.add(transports[i]);
|
||||
if (!transportsList.contains(transport)) {
|
||||
transportsList.add(transport);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* The HTTP status and unparsed header fields of a single HTTP message. Values
|
||||
@@ -248,6 +249,15 @@ public final class RawHeaders {
|
||||
return namesAndValues.get(fieldNameIndex);
|
||||
}
|
||||
|
||||
/** Returns an immutable case-insensitive set of header names. */
|
||||
public Set<String> names() {
|
||||
TreeSet<String> result = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (int i = 0; i < length(); i++) {
|
||||
result.add(getFieldName(i));
|
||||
}
|
||||
return Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
/** Returns the value at {@code index} or null if that is out of range. */
|
||||
public String getValue(int index) {
|
||||
int valueIndex = index * 2 + 1;
|
||||
@@ -267,6 +277,20 @@ public final class RawHeaders {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns an immutable list of the header values for {@code name}. */
|
||||
public List<String> values(String name) {
|
||||
List<String> result = null;
|
||||
for (int i = 0; i < length(); i++) {
|
||||
if (name.equalsIgnoreCase(getFieldName(i))) {
|
||||
if (result == null) result = new ArrayList<String>(2);
|
||||
result.add(getValue(i));
|
||||
}
|
||||
}
|
||||
return result != null
|
||||
? Collections.unmodifiableList(result)
|
||||
: Collections.<String>emptyList();
|
||||
}
|
||||
|
||||
/** @param fieldNames a case-insensitive set of HTTP header field names. */
|
||||
public RawHeaders getAll(Set<String> fieldNames) {
|
||||
RawHeaders result = new RawHeaders();
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import com.google.mockwebserver.MockResponse;
|
||||
import com.google.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class AsyncApiTest {
|
||||
private MockWebServer server = new MockWebServer();
|
||||
private OkHttpClient client = new OkHttpClient();
|
||||
private RecordingReceiver receiver = new RecordingReceiver();
|
||||
|
||||
@After public void tearDown() throws Exception {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Test public void get() throws Exception {
|
||||
server.enqueue(new MockResponse()
|
||||
.setBody("abc")
|
||||
.addHeader("Content-Type: text/plain"));
|
||||
server.play();
|
||||
|
||||
Request request = new Request.Builder(server.getUrl("/"))
|
||||
.header("User-Agent", "AsyncApiTest")
|
||||
.build();
|
||||
client.enqueue(request, receiver);
|
||||
|
||||
receiver.await(request)
|
||||
.assertCode(200)
|
||||
.assertContainsHeaders("Content-Type: text/plain")
|
||||
.assertBody("abc");
|
||||
|
||||
assertTrue(server.takeRequest().getHeaders().contains("User-Agent: AsyncApiTest"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import com.squareup.okhttp.Failure;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* A received response or failure recorded by the response recorder.
|
||||
*/
|
||||
public class RecordedResponse {
|
||||
public final Request request;
|
||||
public final Response response;
|
||||
public final String body;
|
||||
public final Failure failure;
|
||||
|
||||
RecordedResponse(Request request, Response response, String body, Failure failure) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.body = body;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
public RecordedResponse assertCode(int expectedCode) {
|
||||
assertEquals(expectedCode, response.code());
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecordedResponse assertContainsHeaders(String... expectedHeaders) {
|
||||
List<String> actualHeaders = new ArrayList<String>();
|
||||
for (int i = 0; i < response.headerCount(); i++) {
|
||||
actualHeaders.add(response.headerName(i) + ": " + response.headerValue(i));
|
||||
}
|
||||
if (!actualHeaders.containsAll(Arrays.asList(expectedHeaders))) {
|
||||
fail("Expected: " + actualHeaders + "\nto contain: " + Arrays.toString(expectedHeaders));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public RecordedResponse assertBody(String expectedBody) {
|
||||
assertEquals(expectedBody, body);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import com.squareup.okhttp.Failure;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Records received HTTP responses so they can be later retrieved by tests.
|
||||
*/
|
||||
public class RecordingReceiver implements Response.Receiver {
|
||||
public static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
private final List<RecordedResponse> responses = new ArrayList<RecordedResponse>();
|
||||
|
||||
@Override public synchronized void onFailure(Failure failure) {
|
||||
responses.add(new RecordedResponse(failure.request(), null, null, failure));
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@Override public synchronized void onResponse(Response response) throws IOException {
|
||||
responses.add(new RecordedResponse(
|
||||
response.request(), response, response.body().string(), null));
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recorded response triggered by {@code request}. Throws if the
|
||||
* response isn't enqueued before the timeout.
|
||||
*/
|
||||
public synchronized RecordedResponse await(Request request) throws Exception {
|
||||
long timeoutMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) + TIMEOUT_MILLIS;
|
||||
while (true) {
|
||||
for (RecordedResponse recordedResponse : responses) {
|
||||
if (recordedResponse.request == request) {
|
||||
return recordedResponse;
|
||||
}
|
||||
}
|
||||
|
||||
long nowMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
|
||||
if (nowMillis >= timeoutMillis) break;
|
||||
wait(timeoutMillis - nowMillis);
|
||||
}
|
||||
|
||||
throw new AssertionError("Timed out waiting for response to " + request);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user