package com.koushikdutta.ion; import android.app.ProgressDialog; import android.graphics.Bitmap; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.widget.ImageView; import android.widget.ProgressBar; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.DataSink; import com.koushikdutta.async.DataTrackingEmitter; import com.koushikdutta.async.DataTrackingEmitter.DataTracker; import com.koushikdutta.async.FilteredDataEmitter; import com.koushikdutta.async.Util; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.future.Future; import com.koushikdutta.async.future.FutureCallback; import com.koushikdutta.async.future.SimpleFuture; import com.koushikdutta.async.future.TransformFuture; import com.koushikdutta.async.http.AsyncHttpGet; import com.koushikdutta.async.http.AsyncHttpPost; import com.koushikdutta.async.http.AsyncHttpRequest; import com.koushikdutta.async.http.Headers; import com.koushikdutta.async.http.Multimap; import com.koushikdutta.async.http.body.AsyncHttpRequestBody; import com.koushikdutta.async.http.body.DocumentBody; import com.koushikdutta.async.http.body.FileBody; import com.koushikdutta.async.http.body.FilePart; import com.koushikdutta.async.http.body.MultipartFormDataBody; import com.koushikdutta.async.http.body.Part; import com.koushikdutta.async.http.body.StreamBody; import com.koushikdutta.async.http.body.StringBody; import com.koushikdutta.async.http.body.UrlEncodedFormBody; import com.koushikdutta.async.http.server.AsyncHttpServer; import com.koushikdutta.async.parser.AsyncParser; import com.koushikdutta.async.parser.ByteBufferListParser; import com.koushikdutta.async.parser.DocumentParser; import com.koushikdutta.async.parser.StringParser; import com.koushikdutta.async.stream.FileDataSink; import com.koushikdutta.async.stream.OutputStreamDataSink; import com.koushikdutta.ion.Loader.LoaderEmitter; import com.koushikdutta.ion.bitmap.BitmapInfo; import com.koushikdutta.ion.bitmap.LocallyCachedStatus; import com.koushikdutta.ion.builder.Builders; import com.koushikdutta.ion.builder.FutureBuilder; import com.koushikdutta.ion.builder.LoadBuilder; import com.koushikdutta.ion.future.ImageViewFuture; import com.koushikdutta.ion.future.ResponseFuture; import com.koushikdutta.ion.gson.GsonArrayParser; import com.koushikdutta.ion.gson.GsonBody; import com.koushikdutta.ion.gson.GsonObjectParser; import com.koushikdutta.ion.gson.GsonSerializer; import com.koushikdutta.ion.gson.PojoBody; import org.apache.http.NameValuePair; import org.w3c.dom.Document; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Created by koush on 5/21/13. */ class IonRequestBuilder implements Builders.Any.B, Builders.Any.F, Builders.Any.M, Builders.Any.U, LoadBuilder<Builders.Any.B> { Ion ion; ContextReference contextReference; Handler handler = Ion.mainHandler; String method = AsyncHttpGet.METHOD; String uri; public IonRequestBuilder(ContextReference contextReference, Ion ion) { String alive = contextReference.isAlive(); if (null != alive) Log.w("Ion", "Building request with dead context: " + alive); this.ion = ion; this.contextReference = contextReference; } @Override public IonRequestBuilder load(String url) { return loadInternal(AsyncHttpGet.METHOD, url); } private IonRequestBuilder loadInternal(String method, String url) { this.method = method; if (!TextUtils.isEmpty(url) && url.startsWith("/")) url = new File(url).toURI().toString(); this.uri = url; return this; } boolean methodWasSet; @Override public IonRequestBuilder load(String method, String url) { methodWasSet = true; return loadInternal(method, url); } Headers headers; private Headers getHeaders() { if (headers == null) { headers = new Headers(); AsyncHttpRequest.setDefaultHeaders(headers, uri == null ? null : Uri.parse(uri)); } return headers; } @Override public IonRequestBuilder userAgent(String userAgent) { if (TextUtils.isEmpty(userAgent)) return this; return setHeader("User-Agent", userAgent); } @Override public IonRequestBuilder setHeader(String name, String value) { if (value == null) getHeaders().removeAll(name); else getHeaders().set(name, value); return this; } @Override public IonRequestBuilder addHeader(String name, String value) { if (value != null) getHeaders().add(name, value); return this; } @Override public IonRequestBuilder addHeaders(Map<String, List<String>> params) { if (params == null) return this; Headers headers = getHeaders(); for (Map.Entry<String, List<String>> entry: params.entrySet()) { headers.addAll(entry.getKey(), entry.getValue()); } return this; } boolean noCache; @Override public Builders.Any.B noCache() { noCache = true; return setHeader("Cache-Control", "no-cache"); } Multimap query; @Override public IonRequestBuilder addQuery(String name, String value) { if (value == null) return this; if (query == null) query = new Multimap(); query.add(name, value); return this; } @Override public IonRequestBuilder addQueries(Map<String, List<String>> params) { if (query == null) query = new Multimap(); query.putAll(params); return this; } int timeoutMilliseconds = AsyncHttpRequest.DEFAULT_TIMEOUT; @Override public IonRequestBuilder setTimeout(int timeoutMilliseconds) { this.timeoutMilliseconds = timeoutMilliseconds; return this; } @Override public IonRequestBuilder setHandler(Handler handler) { this.handler = handler; return this; } AsyncHttpRequestBody body; private <T> IonRequestBuilder setBody(AsyncHttpRequestBody<T> body) { if (!methodWasSet) method = AsyncHttpPost.METHOD; this.body = body; return this; } @Override public IonRequestBuilder setJsonObjectBody(JsonObject jsonObject) { return setBody(new GsonBody<JsonObject>(ion.configure().getGson(), jsonObject)); } @Override public IonRequestBuilder setJsonArrayBody(JsonArray jsonArray) { return setBody(new GsonBody<JsonArray>(ion.configure().getGson(), jsonArray)); } @Override public IonRequestBuilder setStringBody(String string) { return setBody(new StringBody(string)); } boolean followRedirect = true; @Override public IonRequestBuilder followRedirect(boolean follow) { followRedirect = follow; return this; } private <T> void postExecute(final EmitterTransform<T> future, final Exception ex, final T value) { final Runnable runner = new Runnable() { @Override public void run() { // check if the context is still alive... String deadReason = contextReference.isAlive(); if (deadReason != null) { future.initialRequest.logd("context has died: " + deadReason); future.cancelSilently(); return; } // unless we're invoked onto the handler/main/service thread, there's no frakking way to avoid a // race condition where the service or activity dies before this callback is invoked. if (ex != null) future.setComplete(ex); else future.setComplete(value); } }; if (handler == null) ion.httpClient.getServer().post(runner); else AsyncServer.post(handler, runner); } private Uri prepareURI() { Uri uri; try { if (query != null) { Uri.Builder builder = Uri.parse(this.uri).buildUpon(); for (String key: query.keySet()) { for (String value: query.get(key)) { builder = builder.appendQueryParameter(key, value); } } uri = builder.build(); } else { uri = Uri.parse(this.uri); } } catch (Exception e) { uri = null; } if (uri == null || uri.getScheme() == null) return null; return uri; } private AsyncHttpRequest prepareRequest(Uri uri) { AsyncHttpRequest request = ion.configure().getAsyncHttpRequestFactory().createAsyncHttpRequest(uri, method, headers); request.setFollowRedirect(followRedirect); request.setBody(body); request.setLogging(ion.logtag, ion.logLevel); if (logTag != null) request.setLogging(logTag, logLevel); request.enableProxy(proxyHost, proxyPort); request.setTimeout(timeoutMilliseconds); request.logd("preparing request"); return request; } static interface LoadRequestCallback { boolean loadRequest(AsyncHttpRequest request); } LoadRequestCallback loadRequestCallback; private <T> void getLoaderEmitter(final EmitterTransform<T> ret) { Uri uri = prepareURI(); if (uri == null) { ret.setComplete(new Exception("Invalid URI")); return; } AsyncHttpRequest request = prepareRequest(uri); ret.initialRequest = request; getLoaderEmitter(ret, request); } private <T> void getLoaderEmitter(final EmitterTransform<T> ret, AsyncHttpRequest request) { if (body != null && (uploadProgressHandler != null || uploadProgressBar != null || uploadProgress != null || uploadProgressDialog != null)) { AsyncHttpRequestBody wrappedBody = new RequestBodyUploadObserver(body, new ProgressCallback() { @Override public void onProgress(final long downloaded, final long total) { assert Thread.currentThread() != Looper.getMainLooper().getThread(); final int percent = (int)((float)downloaded / total * 100f); if (uploadProgressBar != null) uploadProgressBar.setProgress(percent); if (uploadProgressDialog != null) uploadProgressDialog.setProgress(percent); if (uploadProgress != null) uploadProgress.onProgress(downloaded, total); if (uploadProgressHandler != null) { AsyncServer.post(Ion.mainHandler, new Runnable() { @Override public void run() { if (ret.isCancelled() || ret.isDone()) return; uploadProgressHandler.onProgress(downloaded, total); } }); } } }); request.setBody(wrappedBody); } resolveAndLoadRequest(request, ret); } <T> void resolveAndLoadRequest(final AsyncHttpRequest request, final EmitterTransform<T> ret) { Future<AsyncHttpRequest> resolved = resolveRequest(request, ret); if (resolved != null) { resolved.setCallback(new FutureCallback<AsyncHttpRequest>() { @Override public void onCompleted(Exception e, final AsyncHttpRequest result) { if (e != null) { ret.setComplete(e); return; } ret.finalRequest = result; resolveAndLoadRequest(result, ret); } }); return; } if (Looper.getMainLooper().getThread() != Thread.currentThread()) { AsyncServer.post(Ion.mainHandler, new Runnable() { @Override public void run() { invokeLoadRequest(request, ret); } }); return; } invokeLoadRequest(request, ret); } <T> void invokeLoadRequest(final AsyncHttpRequest request, final EmitterTransform<T> ret) { if (loadRequestCallback == null || loadRequestCallback.loadRequest(request)) loadRequest(request, ret); } <T> void loadRequest(AsyncHttpRequest request, final EmitterTransform<T> ret) { // now attempt to fetch it directly for (Loader loader: ion.loaders) { Future<DataEmitter> emitter = loader.load(ion, request, ret); if (emitter != null) { request.logi("Using loader: " + loader); ret.setParent(emitter); return; } } ret.setComplete(new Exception("Unknown uri scheme")); } <T> Future<AsyncHttpRequest> resolveRequest(AsyncHttpRequest request, final EmitterTransform<T> ret) { // first attempt to resolve the url for (Loader loader: ion.loaders) { Future<AsyncHttpRequest> resolved = loader.resolve(contextReference.getContext(), ion, request); if (resolved != null) return resolved; } return null; } // transforms a LoaderEmitter, which is a DataEmitter and all associated properties about the data source // into the final result. class EmitterTransform<T> extends TransformFuture<T, LoaderEmitter> implements ResponseFuture<T> { AsyncHttpRequest initialRequest; AsyncHttpRequest finalRequest; ResponseServedFrom servedFrom; Runnable cancelCallback; HeadersResponse headers; DataEmitter emitter; public Response<T> getResponse(Exception e, T result) { return new Response<T>(finalRequest, servedFrom, headers, e, result); } @Override public Future<Response<T>> withResponse() { final SimpleFuture<Response<T>> ret = new SimpleFuture<Response<T>>(); setCallback(new FutureCallback<T>() { @Override public void onCompleted(Exception e, T result) { if (emitter != null) { ret.setComplete(getResponse(e, result)); return; } ret.setComplete(e, null); } }); ret.setParent(this); return ret; } public EmitterTransform(Runnable cancelCallback) { this.cancelCallback = cancelCallback; ion.addFutureInFlight(this, contextReference.getContext()); if (groups == null) return; for (WeakReference<Object> ref: groups) { Object group = ref.get(); if (group != null) ion.addFutureInFlight(this, group); } } @Override protected void cancelCleanup() { super.cancelCleanup(); if (emitter != null) emitter.close(); if (cancelCallback != null) cancelCallback.run(); } @Override protected void error(Exception e) { // don't call superclass which calls setComplete... get onto handler thread. postExecute(this, e, null); } @Override protected void transform(LoaderEmitter emitter) throws Exception { this.emitter = emitter.getDataEmitter(); this.servedFrom = emitter.getServedFrom(); this.headers = emitter.getHeaders(); this.finalRequest = emitter.getRequest(); if (headersCallback != null) { final HeadersResponse headers = emitter.getHeaders(); // what do we do on loaders that don't have headers? files, content://, etc. AsyncServer.post(handler, new Runnable() { @Override public void run() { headersCallback.onHeaders(headers); } }); } // hook up data progress callbacks final long total = emitter.length(); DataTrackingEmitter tracker; if (!(this.emitter instanceof DataTrackingEmitter)) { tracker = new FilteredDataEmitter(); tracker.setDataEmitter(this.emitter); } else { tracker = (DataTrackingEmitter)this.emitter; } this.emitter = tracker; tracker.setDataTracker(new DataTracker() { int lastPercent; @Override public void onData(final int totalBytesRead) { assert Thread.currentThread() != Looper.getMainLooper().getThread(); // if the requesting context dies during the transfer... cancel String deadReason = contextReference.isAlive(); if (deadReason != null) { initialRequest.logd("context has died, cancelling"); cancelSilently(); return; } final int percent = (int)((float)totalBytesRead / total * 100f); if ((progressBar != null || progressDialog != null) && percent != lastPercent) { AsyncServer.post(Ion.mainHandler, new Runnable() { @Override public void run() { if (progressBar != null) { ProgressBar bar = progressBar.get(); if (bar != null) bar.setProgress(percent); } if (progressDialog != null) { ProgressDialog dlg = progressDialog.get(); if (dlg != null) dlg.setProgress(percent); } } }); } lastPercent = percent; if (progress != null) progress.onProgress(totalBytesRead, total); if (progressHandler != null) { AsyncServer.post(Ion.mainHandler, new Runnable() { @Override public void run() { if (isCancelled() || isDone()) return; progressHandler.onProgress(totalBytesRead, total); } }); } } }); } } @Override public IonRequestBuilder progressBar(ProgressBar progressBar) { this.progressBar = new WeakReference<ProgressBar>(progressBar); return this; } @Override public IonRequestBuilder progressDialog(ProgressDialog progressDialog) { this.progressDialog = new WeakReference<ProgressDialog>(progressDialog); return this; } WeakReference<ProgressBar> progressBar; WeakReference<ProgressDialog> progressDialog; ProgressCallback progress; @Override public IonRequestBuilder progress(ProgressCallback callback) { progress = callback; return this; } ProgressCallback progressHandler; @Override public IonRequestBuilder progressHandler(ProgressCallback callback) { progressHandler = callback; return this; } <T> EmitterTransform<T> execute(final DataSink sink, final boolean close, final T result) { return execute(sink, close, result, null); } <T> EmitterTransform<T> execute(final DataSink sink, final boolean close, final T result, final Runnable cancel) { EmitterTransform<T> ret = new EmitterTransform<T>(cancel) { @Override protected void cleanup() { super.cleanup(); if (close) sink.end(); } EmitterTransform<T> self = this; @Override protected void transform(LoaderEmitter emitter) throws Exception { super.transform(emitter); Util.pump(this.emitter, sink, new CompletedCallback() { @Override public void onCompleted(Exception ex) { postExecute(self, ex, result); } }); } }; getLoaderEmitter(ret); return ret; } <T> ResponseFuture<T> execute(final AsyncParser<T> parser) { return execute(parser, null); } <T> ResponseFuture<T> execute(final AsyncParser<T> parser, Runnable cancel) { assert parser != null; final Uri uri = prepareURI(); AsyncHttpRequest request = null; if (uri != null) { request = prepareRequest(uri); Type type = parser.getType(); for (Loader loader: ion.loaders) { ResponseFuture<T> quickLoad = loader.load(ion, request, type); if (quickLoad != null) return quickLoad; } } EmitterTransform<T> ret = new EmitterTransform<T>(cancel) { EmitterTransform<T> self = this; @Override protected void transform(LoaderEmitter emitter) throws Exception { super.transform(emitter); parser.parse(this.emitter).setCallback(new FutureCallback<T>() { @Override public void onCompleted(Exception e, T result) { postExecute(self, e, result); } }); } }; if (uri == null) { ret.setComplete(new Exception("Invalid URI")); return ret; } ret.initialRequest = request; getLoaderEmitter(ret); return ret; } @Override public ResponseFuture<JsonObject> asJsonObject() { return execute(new GsonObjectParser()); } @Override public ResponseFuture<JsonArray> asJsonArray() { return execute(new GsonArrayParser()); } @Override public ResponseFuture<JsonObject> asJsonObject(Charset charset) { return execute(new GsonObjectParser(charset)); } @Override public ResponseFuture<JsonArray> asJsonArray(Charset charset) { return execute(new GsonArrayParser(charset)); } @Override public ResponseFuture<String> asString() { return execute(new StringParser()); } @Override public ResponseFuture<String> asString(Charset charset) { return execute(new StringParser(charset)); } @Override public ResponseFuture<byte[]> asByteArray() { return execute(new AsyncParser<byte[]>() { @Override public Future<byte[]> parse(DataEmitter emitter) { return new ByteBufferListParser().parse(emitter) .then(new TransformFuture<byte[], ByteBufferList>() { @Override protected void transform(ByteBufferList result) throws Exception { setComplete(result.getAllByteArray()); } }); } @Override public void write(DataSink sink, byte[] value, CompletedCallback completed) { new ByteBufferListParser().write(sink, new ByteBufferList(value), completed); } @Override public Type getType() { return byte[].class; } }); } @Override public ResponseFuture<InputStream> asInputStream() { return execute(new InputStreamParser()); } @Override public ResponseFuture<DataEmitter> asDataEmitter() { return execute(new DataEmitterParser()); } @Override public <T> ResponseFuture<T> as(AsyncParser<T> parser) { return execute(parser); } @Override public <F extends OutputStream> ResponseFuture<F> write(F outputStream, boolean close) { return execute(new OutputStreamDataSink(ion.getServer(), outputStream), close, outputStream); } @Override public <F extends OutputStream> ResponseFuture<F> write(F outputStream) { return execute(new OutputStreamDataSink(ion.getServer(), outputStream), true, outputStream); } @Override public EmitterTransform<File> write(final File file) { return execute(new FileDataSink(ion.getServer(), file), true, file, new Runnable() { @Override public void run() { file.delete(); } }); } Multimap bodyParameters; @Override public IonRequestBuilder setBodyParameter(String name, String value) { if (bodyParameters == null) { bodyParameters = new Multimap(); setBody(new UrlEncodedFormBody(bodyParameters)); } if (value != null) bodyParameters.add(name, value); return this; } public IonRequestBuilder setBodyParameters(Map<String, List<String>> params) { if (bodyParameters == null) { bodyParameters = new Multimap(); setBody(new UrlEncodedFormBody(bodyParameters)); } bodyParameters.putAll(params); return this; } MultipartFormDataBody multipartBody; @Override public IonRequestBuilder setMultipartFile(String name, File file) { return setMultipartFile(name, null, file); } @Override public IonRequestBuilder setMultipartFile(String name, String contentType, File file) { if (multipartBody == null) { multipartBody = new MultipartFormDataBody(); setBody(multipartBody); } FilePart part = new FilePart(name, file); if (contentType == null) contentType = AsyncHttpServer.tryGetContentType(file.getAbsolutePath()); if (contentType != null) part.setContentType(contentType); multipartBody.addPart(part); return this; } @Override public IonRequestBuilder setMultipartParameter(String name, String value) { if (multipartBody == null) { multipartBody = new MultipartFormDataBody(); setBody(multipartBody); } if (value != null) multipartBody.addStringPart(name, value); return this; } @Override public IonRequestBuilder setMultipartParameters(Map<String, List<String>> params) { for (String key: params.keySet()) { for (String value: params.get(key)) { if (value != null) setMultipartParameter(key, value); } } return this; } @Override public IonRequestBuilder addMultipartParts(Iterable<Part> parameters) { if (multipartBody == null) { multipartBody = new MultipartFormDataBody(); setBody(multipartBody); } for (Part part: parameters) { multipartBody.addPart(part); } return this; } @Override public Builders.Any.M addMultipartParts(Part... parameters) { if (multipartBody == null) { multipartBody = new MultipartFormDataBody(); setBody(multipartBody); } for (Part part: parameters) { multipartBody.addPart(part); } return this; } @Override public IonRequestBuilder setMultipartContentType(String contentType) { if (multipartBody == null) { multipartBody = new MultipartFormDataBody(); setBody(multipartBody); } multipartBody.setContentType(contentType); return this; } @Override public IonImageViewRequestBuilder withBitmap() { return new IonImageViewRequestBuilder(this); } @Override public ImageViewFuture intoImageView(ImageView imageView) { return new IonImageViewRequestBuilder(this).withImageView(imageView).intoImageView(imageView); } @Override public IonRequestBuilder load(File file) { loadInternal(null, file.toURI().toString()); return this; } @Override public BitmapInfo asCachedBitmap() { return new IonImageViewRequestBuilder(this).asCachedBitmap(); } @Override public LocallyCachedStatus isLocallyCached() { return new IonImageViewRequestBuilder(this).isLocallyCached(); } @Override public Future<Bitmap> asBitmap() { return new IonImageViewRequestBuilder(this).asBitmap(); } String logTag; int logLevel; @Override public IonRequestBuilder setLogging(String tag, int level) { logTag = tag; logLevel = level; return this; } @Override public <T> ResponseFuture<T> as(Class<T> clazz) { return execute(new GsonSerializer<T>(ion.configure().getGson(), clazz)); } @Override public <T> ResponseFuture<T> as(TypeToken<T> token) { return execute(new GsonSerializer<T>(ion.configure().getGson(), token)); } ArrayList<WeakReference<Object>> groups; @Override public FutureBuilder group(Object groupKey) { if (groups == null) groups = new ArrayList<WeakReference<Object>>(); groups.add(new WeakReference<Object>(groupKey)); return this; } String proxyHost; int proxyPort; @Override public IonRequestBuilder proxy(String host, int port) { proxyHost = host; proxyPort = port; return this; } @Override public IonRequestBuilder setJsonPojoBody(Object object, TypeToken token) { setBody(new PojoBody(ion.configure().getGson(), object, token)); return this; } @Override public IonRequestBuilder setJsonPojoBody(Object object) { setBody(new PojoBody(ion.configure().getGson(), object, null)); return this; } @Override public IonRequestBuilder basicAuthentication(String username, String password) { return setHeader("Authorization", "Basic " + Base64.encodeToString(String.format("%s:%s", username, password).getBytes(), Base64.NO_WRAP)); } ProgressCallback uploadProgress; @Override public Builders.Any.B uploadProgress(ProgressCallback callback) { uploadProgress = callback; return this; } ProgressBar uploadProgressBar; @Override public Builders.Any.B uploadProgressBar(ProgressBar progressBar) { uploadProgressBar = progressBar; return this; } ProgressDialog uploadProgressDialog; @Override public Builders.Any.B uploadProgressDialog(ProgressDialog progressDialog) { uploadProgressDialog = progressDialog; return this; } ProgressCallback uploadProgressHandler; @Override public Builders.Any.B uploadProgressHandler(ProgressCallback callback) { uploadProgressHandler = callback; return this; } HeadersCallback headersCallback; @Override public Builders.Any.B onHeaders(HeadersCallback callback) { headersCallback = callback; return this; } @Override public Builders.Any.F setDocumentBody(Document document) { setBody(new DocumentBody(document)); return this; } @Override public ResponseFuture<Document> asDocument() { return execute(new DocumentParser()); } @Override public Builders.Any.F setFileBody(File file) { setBody(new FileBody(file)); return this; } @Override public Builders.Any.F setByteArrayBody(byte[] bytes) { if (bytes != null) setBody(new StreamBody(new ByteArrayInputStream(bytes), bytes.length)); return this; } @Override public Builders.Any.F setStreamBody(InputStream inputStream) { setBody(new StreamBody(inputStream, -1)); return this; } @Override public Builders.Any.F setStreamBody(InputStream inputStream, int length) { setBody(new StreamBody(inputStream, length)); return this; } @Override public Builders.Any.B setHeader(NameValuePair... header) { Headers headers = getHeaders(); for (NameValuePair h: header) { headers.set(h.getName(), h.getValue()); } return this; } }