/* * Copyright 2010 Ning, Inc. * * Ning 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.ning.http.client; import java.io.File; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.URI; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.ning.http.client.Request.EntityWriter; import com.ning.http.util.UTF8UrlEncoder; /** * Builder for {@link Request} * * @param <T> */ public abstract class RequestBuilderBase<T extends RequestBuilderBase<T>> { private static final class RequestImpl implements Request { private String method; private String url = null; private InetAddress address = null; private FluentCaseInsensitiveStringsMap headers = new FluentCaseInsensitiveStringsMap(); private Collection<Cookie> cookies = new ArrayList<Cookie>(); private byte[] byteData; private String stringData; private InputStream streamData; private EntityWriter entityWriter; private BodyGenerator bodyGenerator; private FluentStringsMap params; private List<Part> parts; private String virtualHost; private long length = -1; public FluentStringsMap queryParams; public ProxyServer proxyServer; private Realm realm; private File file; private Boolean followRedirects; private PerRequestConfig perRequestConfig; private long rangeOffset = 0; public String charset; private boolean useRawUrl = false; public RequestImpl(final boolean useRawUrl) { this.useRawUrl = useRawUrl; } public RequestImpl(final Request prototype) { if (prototype != null) { this.method = prototype.getMethod(); final int pos = prototype.getUrl().indexOf("?"); this.url = pos > 0 ? prototype.getUrl().substring(0, pos) : prototype.getUrl(); this.address = prototype.getInetAddress(); this.headers = new FluentCaseInsensitiveStringsMap( prototype.getHeaders()); this.cookies = new ArrayList<Cookie>(prototype.getCookies()); this.byteData = prototype.getByteData(); this.stringData = prototype.getStringData(); this.streamData = prototype.getStreamData(); this.entityWriter = prototype.getEntityWriter(); this.bodyGenerator = prototype.getBodyGenerator(); this.params = (prototype.getParams() == null ? null : new FluentStringsMap(prototype.getParams())); this.queryParams = (prototype.getQueryParams() == null ? null : new FluentStringsMap(prototype.getQueryParams())); this.parts = (prototype.getParts() == null ? null : new ArrayList<Part>(prototype.getParts())); this.virtualHost = prototype.getVirtualHost(); this.length = prototype.getContentLength(); this.proxyServer = prototype.getProxyServer(); this.realm = prototype.getRealm(); this.file = prototype.getFile(); this.followRedirects = prototype.isRedirectOverrideSet() ? prototype .isRedirectEnabled() : null; this.perRequestConfig = prototype.getPerRequestConfig(); this.rangeOffset = prototype.getRangeOffset(); this.charset = prototype.getBodyEncoding(); this.useRawUrl = prototype.isUseRawUrl(); } } /* @Override */ @Override public String getReqType() { return getMethod(); } @Override public String getMethod() { return method; } /* @Override */ @Override public String getUrl() { return toUrl(true); } @Override public InetAddress getInetAddress() { return address; } private String toUrl(final boolean encode) { if (url == null) { url = "http://localhost"; } String uri = url; if (!uri.startsWith("ws")) { try { uri = URI.create(url).toURL().toString(); } catch (final Throwable e) { throw new IllegalArgumentException("Illegal URL: " + url, e); } } if (queryParams != null && !queryParams.isEmpty()) { final StringBuilder builder = new StringBuilder(); if (!url.substring(8).contains("/")) { // no other "/" than // http[s]:// -> // http://localhost:1234 builder.append("/"); } builder.append("?"); for (final Iterator<Entry<String, List<String>>> i = queryParams .iterator(); i.hasNext();) { final Map.Entry<String, List<String>> param = i.next(); final String name = param.getKey(); for (final Iterator<String> j = param.getValue().iterator(); j .hasNext();) { final String value = j.next(); if (encode) { UTF8UrlEncoder.appendEncoded(builder, name); } else { builder.append(name); } if (value != null && !value.equals("")) { builder.append('='); if (encode) { UTF8UrlEncoder.appendEncoded(builder, value); } else { builder.append(value); } } if (j.hasNext()) { builder.append('&'); } } if (i.hasNext()) { builder.append('&'); } } uri += builder.toString(); } return uri; } /* @Override */ @Override public String getRawUrl() { return toUrl(false); } /* @Override */ @Override public FluentCaseInsensitiveStringsMap getHeaders() { return headers; } /* @Override */ @Override public Collection<Cookie> getCookies() { return Collections.unmodifiableCollection(cookies); } /* @Override */ @Override public byte[] getByteData() { return byteData; } /* @Override */ @Override public String getStringData() { return stringData; } /* @Override */ @Override public InputStream getStreamData() { return streamData; } /* @Override */ @Override public EntityWriter getEntityWriter() { return entityWriter; } /* @Override */ @Override public BodyGenerator getBodyGenerator() { return bodyGenerator; } /* @Override */ /** * @return * @deprecated */ @Deprecated @Override public long getLength() { return length; } @Override public long getContentLength() { return length; } /* @Override */ @Override public FluentStringsMap getParams() { return params; } /* @Override */ @Override public List<Part> getParts() { return parts; } /* @Override */ @Override public String getVirtualHost() { return virtualHost; } @Override public FluentStringsMap getQueryParams() { return queryParams; } @Override public ProxyServer getProxyServer() { return proxyServer; } @Override public Realm getRealm() { return realm; } @Override public File getFile() { return file; } @Override public boolean isRedirectEnabled() { return (followRedirects != null && followRedirects); } @Override public boolean isRedirectOverrideSet() { return followRedirects != null; } @Override public PerRequestConfig getPerRequestConfig() { return perRequestConfig; } @Override public long getRangeOffset() { return rangeOffset; } @Override public String getBodyEncoding() { return charset; } @Override public String toString() { final StringBuilder sb = new StringBuilder(url); sb.append("\t"); sb.append(method); for (final String name : headers.keySet()) { sb.append("\t"); sb.append(name); sb.append(":"); sb.append(headers.getJoinedValue(name, ", ")); } return sb.toString(); } @Override public boolean isUseRawUrl() { return useRawUrl; } } private final Class<T> derived; protected final RequestImpl request; protected boolean useRawUrl = false; protected RequestBuilderBase(final Class<T> derived, final String method, final boolean rawUrls) { this.derived = derived; request = new RequestImpl(rawUrls); request.method = method; this.useRawUrl = rawUrls; } protected RequestBuilderBase(final Class<T> derived, final Request prototype) { this.derived = derived; request = new RequestImpl(prototype); this.useRawUrl = prototype.isUseRawUrl(); } public T setUrl(final String url) { request.url = buildUrl(url); return derived.cast(this); } public T setInetAddress(final InetAddress address) { request.address = address; return derived.cast(this); } private String buildUrl(String url) { final URI uri = URI.create(url); final StringBuilder buildedUrl = new StringBuilder(); if (uri.getScheme() != null) { buildedUrl.append(uri.getScheme()); buildedUrl.append("://"); } if (uri.getAuthority() != null) { buildedUrl.append(uri.getAuthority()); } if (uri.getRawPath() != null) { buildedUrl.append(uri.getRawPath()); } else { // AHC-96 // Let's try to derive it if (url.indexOf("://") == -1) { final String s = buildedUrl.toString(); url = s + url.substring(uri.getScheme().length() + 1); return buildUrl(url); } else { throw new IllegalArgumentException("Invalid url " + uri.toString()); } } if (uri.getRawQuery() != null && !uri.getRawQuery().equals("")) { final String[] queries = uri.getRawQuery().split("&"); int pos; for (final String query : queries) { pos = query.indexOf("="); if (pos <= 0) { addQueryParameter(query, null); } else { try { if (this.useRawUrl) { addQueryParameter(query.substring(0, pos), query.substring(pos + 1)); } else { addQueryParameter(URLDecoder.decode( query.substring(0, pos), "UTF-8"), URLDecoder.decode(query.substring(pos + 1), "UTF-8")); } } catch (final UnsupportedEncodingException e) { throw new RuntimeException(e); } } } } return buildedUrl.toString(); } public T setVirtualHost(final String virtualHost) { request.virtualHost = virtualHost; return derived.cast(this); } public T setHeader(final String name, final String value) { request.headers.replace(name, value); return derived.cast(this); } public T addHeader(final String name, String value) { if (value == null) { value = ""; } request.headers.add(name, value); return derived.cast(this); } public T setHeaders(final FluentCaseInsensitiveStringsMap headers) { request.headers = (headers == null ? new FluentCaseInsensitiveStringsMap() : new FluentCaseInsensitiveStringsMap(headers)); return derived.cast(this); } public T setHeaders(final Map<String, Collection<String>> headers) { request.headers = (headers == null ? new FluentCaseInsensitiveStringsMap() : new FluentCaseInsensitiveStringsMap(headers)); return derived.cast(this); } public T setContentLength(final int length) { request.length = length; return derived.cast(this); } public T addCookie(final Cookie cookie) { request.cookies.add(cookie); return derived.cast(this); } private void resetParameters() { request.params = null; } private void resetNonMultipartData() { request.byteData = null; request.stringData = null; request.streamData = null; request.entityWriter = null; request.length = -1; } private void resetMultipartData() { request.parts = null; } private void checkIfBodyAllowed() { if ("GET".equals(request.method) || "HEAD".equals(request.method)) { throw new IllegalArgumentException( "Can NOT set Body on HTTP Request Method GET nor HEAD."); } } public T setBody(final File file) { checkIfBodyAllowed(); request.file = file; return derived.cast(this); } public T setBody(final byte[] data) throws IllegalArgumentException { checkIfBodyAllowed(); resetParameters(); resetNonMultipartData(); resetMultipartData(); request.byteData = data; return derived.cast(this); } public T setBody(final String data) throws IllegalArgumentException { checkIfBodyAllowed(); resetParameters(); resetNonMultipartData(); resetMultipartData(); request.stringData = data; return derived.cast(this); } public T setBody(final InputStream stream) throws IllegalArgumentException { checkIfBodyAllowed(); resetParameters(); resetNonMultipartData(); resetMultipartData(); request.streamData = stream; return derived.cast(this); } public T setBody(final EntityWriter dataWriter) { return setBody(dataWriter, -1); } public T setBody(final EntityWriter dataWriter, final long length) throws IllegalArgumentException { checkIfBodyAllowed(); resetParameters(); resetNonMultipartData(); resetMultipartData(); request.entityWriter = dataWriter; request.length = length; return derived.cast(this); } public T setBody(final BodyGenerator bodyGenerator) { checkIfBodyAllowed(); request.bodyGenerator = bodyGenerator; return derived.cast(this); } public T addQueryParameter(final String name, final String value) { if (request.queryParams == null) { request.queryParams = new FluentStringsMap(); } request.queryParams.add(name, value); return derived.cast(this); } public T setQueryParameters(final FluentStringsMap parameters) { if (parameters == null) { request.queryParams = null; } else { request.queryParams = new FluentStringsMap(parameters); } return derived.cast(this); } public T addParameter(final String key, final String value) throws IllegalArgumentException { resetNonMultipartData(); resetMultipartData(); if (request.params == null) { request.params = new FluentStringsMap(); } request.params.add(key, value); return derived.cast(this); } public T setParameters(final FluentStringsMap parameters) throws IllegalArgumentException { resetNonMultipartData(); resetMultipartData(); request.params = new FluentStringsMap(parameters); return derived.cast(this); } public T setParameters(final Map<String, Collection<String>> parameters) throws IllegalArgumentException { resetNonMultipartData(); resetMultipartData(); request.params = new FluentStringsMap(parameters); return derived.cast(this); } public T addBodyPart(final Part part) throws IllegalArgumentException { resetParameters(); resetNonMultipartData(); if (request.parts == null) { request.parts = new ArrayList<Part>(); } request.parts.add(part); return derived.cast(this); } public T setProxyServer(final ProxyServer proxyServer) { request.proxyServer = proxyServer; return derived.cast(this); } public T setRealm(final Realm realm) { request.realm = realm; return derived.cast(this); } public T setFollowRedirects(final boolean followRedirects) { request.followRedirects = followRedirects; return derived.cast(this); } public T setPerRequestConfig(final PerRequestConfig perRequestConfig) { request.perRequestConfig = perRequestConfig; return derived.cast(this); } public T setRangeOffset(final long rangeOffset) { request.rangeOffset = rangeOffset; return derived.cast(this); } public T setMethod(final String method) { request.method = method; return derived.cast(this); } public T setBodyEncoding(final String charset) { request.charset = charset; return derived.cast(this); } public Request build() { if ((request.length < 0) && (request.streamData == null) && allowBody(request.getMethod())) { // can't concatenate content-length final String contentLength = request.headers .getFirstValue("Content-Length"); if (contentLength != null) { try { request.length = Long.parseLong(contentLength); } catch (final NumberFormatException e) { // NoOp -- we wdn't specify length so it will be chunked? } } } return request; } private boolean allowBody(final String method) { if (method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("OPTIONS") && method.equalsIgnoreCase("TRACE") && method.equalsIgnoreCase("HEAD")) { return false; } else { return true; } } public T addOrReplaceCookie(final Cookie cookie) { final String cookieKey = cookie.getName(); boolean replace = false; int index = 0; for (final Cookie c : request.cookies) { if (c.getName().equals(cookieKey)) { replace = true; break; } index++; } if (replace) { ((ArrayList<Cookie>) request.cookies).set(index, cookie); } else { request.cookies.add(cookie); } return derived.cast(this); } }