Java tutorial
/* * Copyright Yan Zhenjie. All Rights Reserved * * 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.yolanda.nohttp; import android.text.TextUtils; import com.yolanda.nohttp.tools.CounterOutputStream; import com.yolanda.nohttp.tools.IOUtils; import com.yolanda.nohttp.tools.LinkedMultiValueMap; import com.yolanda.nohttp.tools.MultiValueMap; import org.json.JSONObject; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.Proxy; import java.net.URLEncoder; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; /** * <p> * Implement all the methods of the base class {@link ImplServerRequest} and {@link ImplClientRequest}. * </p> * Created in Nov 4, 2015 8:28:50 AM. * * @author Yan Zhenjie. */ ; public abstract class BasicRequest implements BasicClientRequest, BasicServerRequest { private final String boundary = createBoundary(); private final String startBoundary = "--" + boundary; private final String endBoundary = startBoundary + "--"; /** * Request priority. */ private Priority mPriority = Priority.DEFAULT; /** * The sequence. */ private int sequence; /** * Target address. */ private String url; /** * The real . */ private String buildUrl; /** * Request method. */ private RequestMethod mRequestMethod; /** * Proxy server. */ private Proxy mProxy; /** * SSLSockets. */ private SSLSocketFactory mSSLSocketFactory = null; /** * HostnameVerifier. */ private HostnameVerifier mHostnameVerifier = null; /** * Connect timeout of request. */ private int mConnectTimeout = NoHttp.TIMEOUT_8S; /** * Read data timeout. */ private int mReadTimeout = NoHttp.TIMEOUT_8S; /** * ContentType */ private String mContentType; /** * Request heads. */ private Headers mHeaders; /** * Param collection. */ private MultiValueMap<String, Object> mParamKeyValues; /** * RequestBody. */ private InputStream mRequestBody; /** * Redirect handler. */ private RedirectHandler mRedirectHandler; /** * Request queue */ private BlockingQueue<?> blockingQueue; /** * The record has started. */ private boolean isStart = false; /** * The request is completed. */ private boolean isFinished = false; /** * Has been canceled. */ private boolean isCanceled = false; /** * Cancel sign. */ private Object cancelSign; /** * Tag of request. */ private Object mTag; /** * Create a request, RequestMethod is {@link RequestMethod#GET}. * * @param url request address, like: http://www.google.com. */ public BasicRequest(String url) { this(url, RequestMethod.GET); } /** * Create a request. * * @param url request adress, like: http://www.google.com. * @param requestMethod request method, like {@link RequestMethod#GET}, {@link RequestMethod#POST}. */ public BasicRequest(String url, RequestMethod requestMethod) { this.url = url; mRequestMethod = requestMethod; mHeaders = new HttpHeaders(); mParamKeyValues = new LinkedMultiValueMap<String, Object>(); } @Override public void setPriority(Priority priority) { this.mPriority = priority; } @Override public Priority getPriority() { return mPriority; } @Override public void setSequence(int sequence) { this.sequence = sequence; } @Override public int getSequence() { return this.sequence; } @Override public final int compareTo(BasicServerRequest another) { final Priority me = getPriority(); final Priority it = another.getPriority(); return me == it ? getSequence() - another.getSequence() : it.ordinal() - me.ordinal(); } @Override public String url() { if (TextUtils.isEmpty(buildUrl)) { StringBuilder urlBuilder = new StringBuilder(url); if (!getRequestMethod().allowRequestBody() && mParamKeyValues.size() > 0) { StringBuffer paramBuffer = buildCommonParams(getParamKeyValues(), getParamsEncoding()); if (url.contains("?") && url.contains("=") && paramBuffer.length() > 0) urlBuilder.append("&"); else if (paramBuffer.length() > 0) urlBuilder.append("?"); urlBuilder.append(paramBuffer); } buildUrl = urlBuilder.toString(); } return buildUrl; } @Override public RequestMethod getRequestMethod() { return mRequestMethod; } @Override public void setProxy(Proxy proxy) { this.mProxy = proxy; } @Override public Proxy getProxy() { return mProxy; } @Override public void setSSLSocketFactory(SSLSocketFactory socketFactory) { mSSLSocketFactory = socketFactory; } @Override public SSLSocketFactory getSSLSocketFactory() { return mSSLSocketFactory; } @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { mHostnameVerifier = hostnameVerifier; } @Override public HostnameVerifier getHostnameVerifier() { return mHostnameVerifier; } @Override public void setConnectTimeout(int connectTimeout) { mConnectTimeout = connectTimeout; } @Override public int getConnectTimeout() { return mConnectTimeout; } @Override public void setReadTimeout(int readTimeout) { mReadTimeout = readTimeout; } @Override public int getReadTimeout() { return mReadTimeout; } @Override public void setContentType(String contentType) { this.mContentType = contentType; } @Override public void addHeader(String key, String value) { mHeaders.add(key, value); } @Override public void setHeader(String key, String value) { mHeaders.set(key, value); } @Override public void removeHeader(String key) { mHeaders.remove(key); } @Override public void removeAllHeader() { mHeaders.clear(); } @Override public Headers headers() { return mHeaders; } @Override public String getAcceptLanguage() { return defaultAcceptLanguage(); } @Override public long getContentLength() { CounterOutputStream outputStream = new CounterOutputStream(); try { onWriteRequestBody(outputStream); } catch (IOException e) { Logger.e(e); } return outputStream.get(); } @Override public String getContentType() { StringBuilder contentTypeBuild = new StringBuilder(); if (getRequestMethod().allowRequestBody() && hasBinary()) contentTypeBuild.append(NoHttp.MULTIPART_FORM_DATA).append("; boundary=").append(boundary); else if (TextUtils.isEmpty(mContentType)) contentTypeBuild.append(NoHttp.APPLICATION_X_WWW_FORM_URLENCODED).append("; charset=") .append(getParamsEncoding()); else contentTypeBuild.append(mContentType); return contentTypeBuild.toString(); } @Override public String getUserAgent() { return UserAgent.instance(); } /** * Get the parameters of key-value pairs. * * @return Not empty Map. */ protected final MultiValueMap<String, Object> getParamKeyValues() { return mParamKeyValues; } @Override public void add(String key, String value) { if (value != null) { if (getRequestMethod().allowRequestBody()) mParamKeyValues.add(key, value); else mParamKeyValues.set(key, value); } } @Override public void set(String key, String value) { if (value != null) mParamKeyValues.set(key, value); } @Override public void add(String key, int value) { add(key, Integer.toString(value)); } @Override public void add(String key, long value) { add(key, Long.toString(value)); } @Override public void add(String key, boolean value) { add(key, String.valueOf(value)); } @Override public void add(String key, char value) { add(key, String.valueOf(value)); } @Override public void add(String key, double value) { add(key, Double.toString(value)); } @Override public void add(String key, float value) { add(key, Float.toString(value)); } @Override public void add(String key, short value) { add(key, Integer.toString(value)); } @Override public void add(String key, byte value) { add(key, Integer.toString(value)); } @Override public void add(String key, Binary binary) { mParamKeyValues.add(key, binary); } @Override public void add(String key, List<Binary> binaries) { if (binaries != null) for (Binary binary : binaries) mParamKeyValues.add(key, binary); else Logger.e("The binaries is null."); } @Override public void set(String key, List<Binary> binaries) { mParamKeyValues.remove(key); if (binaries != null) add(key, binaries); else Logger.e("The binaries is null."); } @Override public void add(Map<String, String> params) { if (params != null) { for (Map.Entry<String, String> stringEntry : params.entrySet()) add(stringEntry.getKey(), stringEntry.getValue()); } } @Override public void set(Map<String, String> params) { if (params != null) { mParamKeyValues.clear(); for (Map.Entry<String, String> stringEntry : params.entrySet()) set(stringEntry.getKey(), stringEntry.getValue()); } } @Override public List<Object> remove(String key) { return mParamKeyValues.remove(key); } @Override public void removeAll() { mParamKeyValues.clear(); } @Override public void setDefineRequestBody(InputStream requestBody, String contentType) { if (requestBody == null || contentType == null) throw new IllegalArgumentException("The requestBody and contentType must be can't be null"); if (requestBody instanceof ByteArrayInputStream || requestBody instanceof FileInputStream) { this.mRequestBody = requestBody; this.mContentType = contentType; } else { throw new IllegalArgumentException( "Can only accept ByteArrayInputStream and FileInputStream type of stream"); } } @Override public void setDefineRequestBody(String requestBody, String contentType) { if (!TextUtils.isEmpty(requestBody)) { try { mRequestBody = IOUtils.toInputStream(requestBody, getParamsEncoding()); if (!TextUtils.isEmpty(mContentType)) mContentType = contentType + "; charset=" + getParamsEncoding(); } catch (UnsupportedEncodingException e) { setDefineRequestBody(IOUtils.toInputStream(requestBody), contentType); } } } @Override public void setDefineRequestBodyForJson(String jsonBody) { if (!TextUtils.isEmpty(jsonBody)) setDefineRequestBody(jsonBody, NoHttp.APPLICATION_JSON); } @Override public void setDefineRequestBodyForJson(JSONObject jsonBody) { if (jsonBody != null) setDefineRequestBody(jsonBody.toString(), NoHttp.APPLICATION_JSON); } @Override public void setDefineRequestBodyForXML(String xmlBody) { if (!TextUtils.isEmpty(xmlBody)) setDefineRequestBody(xmlBody, NoHttp.APPLICATION_XML); } /** * @param body string of request body. * @deprecated use {@link #setDefineRequestBody(String, String)} instead. */ @Deprecated @Override public void setRequestBody(String body) { if (body != null) try { setRequestBody(body.getBytes(getParamsEncoding())); } catch (UnsupportedEncodingException e) { Logger.e( "From getParamsEncoding() returns the charset not supported by the system, the requestBody is invalid, please check the method returns getParamsEncoding() value"); } } /** * @param body byte array of request body. * @deprecated use {@link #setDefineRequestBody(String, String)} instead. */ @Deprecated @Override public void setRequestBody(byte[] body) { if (body != null) this.mRequestBody = new ByteArrayInputStream(body); } @Override public void onPreExecute() { } /** * Is there a custom request inclusions. * * @return Returns true representatives have, return false on behalf of the no. */ protected boolean hasDefineRequestBody() { return mRequestBody != null; } /** * To get custom inclusions. * * @return {@link InputStream}. */ protected InputStream getDefineRequestBody() { return mRequestBody; } @Override public void onWriteRequestBody(OutputStream writer) throws IOException { if (!hasDefineRequestBody() && hasBinary()) writeFormStreamData(writer); else if (!hasDefineRequestBody()) writeCommonStreamData(writer); else writeRequestBody(writer); } /** * Send form data. * * @param writer {@link OutputStream}. * @throws IOException write error. */ protected void writeFormStreamData(OutputStream writer) throws IOException { Set<String> keys = mParamKeyValues.keySet(); for (String key : keys) { List<Object> values = mParamKeyValues.getValues(key); for (Object value : values) { if (value != null && value instanceof String) { if (!(writer instanceof CounterOutputStream)) Logger.i(key + "=" + value); writeFormString(writer, key, value.toString()); } else if (value != null && value instanceof Binary) { if (!(writer instanceof CounterOutputStream)) Logger.i(key + " is Binary"); writeFormBinary(writer, key, (Binary) value); } } } writer.write(("\r\n" + endBoundary).getBytes()); } /** * Send text data in a form. * * @param writer {@link OutputStream} * @param key equivalent to form the name of the input label, {@code "Content-Disposition: form-data; name=key"}. * @param value equivalent to form the value of the input label. * @throws IOException Write the data may be abnormal. */ private void writeFormString(OutputStream writer, String key, String value) throws IOException { StringBuilder stringFieldBuilder = new StringBuilder(startBoundary).append("\r\n"); stringFieldBuilder.append("Content-Disposition: form-data; name=\"").append(key).append("\"\r\n"); stringFieldBuilder.append("Content-Type: text/plain; charset=").append(getParamsEncoding()) .append("\r\n\r\n"); writer.write(stringFieldBuilder.toString().getBytes(getParamsEncoding())); writer.write(value.getBytes(getParamsEncoding())); writer.write("\r\n".getBytes()); } /** * Send binary data in a form. */ private void writeFormBinary(OutputStream writer, String key, Binary value) throws IOException { long contentLength = value.getLength(); if (contentLength > 0) {// Have content to send StringBuilder binaryFieldBuilder = new StringBuilder(startBoundary).append("\r\n"); binaryFieldBuilder.append("Content-Disposition: form-data; name=\"").append(key).append("\""); String filename = value.getFileName(); if (!TextUtils.isEmpty(filename)) binaryFieldBuilder.append("; filename=\"").append(filename).append("\""); binaryFieldBuilder.append("\r\n"); binaryFieldBuilder.append("Content-Type: ").append(value.getMimeType()).append("\r\n"); binaryFieldBuilder.append("Content-Transfer-Encoding: binary\r\n\r\n"); writer.write(binaryFieldBuilder.toString().getBytes()); if (writer instanceof CounterOutputStream) { ((CounterOutputStream) writer).write(contentLength); } else { value.onWriteBinary(writer); } writer.write("\r\n".getBytes()); } } /** * Send non form data. * * @param writer {@link OutputStream}. * @throws IOException write error. */ protected void writeCommonStreamData(OutputStream writer) throws IOException { String requestBody = buildCommonParams(getParamKeyValues(), getParamsEncoding()).toString(); if (!(writer instanceof CounterOutputStream)) Logger.i("Push RequestBody: " + requestBody); writer.write(requestBody.getBytes()); } /** * Send request requestBody. * * @param writer {@link OutputStream}. * @throws IOException write error. */ protected void writeRequestBody(OutputStream writer) throws IOException { if (hasDefineRequestBody()) { if (writer instanceof CounterOutputStream) { writer.write(mRequestBody.available()); } else { IOUtils.write(mRequestBody, writer); IOUtils.closeQuietly(mRequestBody); mRequestBody = null; } } } @Override public void setRedirectHandler(RedirectHandler redirectHandler) { mRedirectHandler = redirectHandler; } @Override public RedirectHandler getRedirectHandler() { return mRedirectHandler; } @Override public void setTag(Object tag) { this.mTag = tag; } @Override public Object getTag() { return this.mTag; } @Override public void setQueue(BlockingQueue<?> queue) { blockingQueue = queue; } @Override public boolean inQueue() { return blockingQueue != null && blockingQueue.contains(this); } @Override public void start() { this.isStart = true; } @Override public boolean isStarted() { return isStart; } @Override public void finish() { this.isFinished = true; } @Override public boolean isFinished() { return isFinished; } /** * Cancel handle. * * @param cancel true or false. * @deprecated use {@link #cancel()} instead. */ @Deprecated @Override public void cancel(boolean cancel) { if (cancel) cancel(); } @Override public void cancel() { if (!isCanceled) { isCanceled = true; if (hasDefineRequestBody()) IOUtils.closeQuietly(getDefineRequestBody()); // cancel file upload Set<String> keys = mParamKeyValues.keySet(); for (String key : keys) { List<Object> values = mParamKeyValues.getValues(key); for (Object value : values) if (value != null && value instanceof Binary) ((Binary) value).cancel(); } } } @Override public boolean isCanceled() { return isCanceled; } @Override public void setCancelSign(Object sign) { this.cancelSign = sign; } @Override public void cancelBySign(Object sign) { if (cancelSign == sign) cancel(); } /** * Returns the data "Charset". * * @return Such as: {@code UTF-8}. */ public String getParamsEncoding() { return NoHttp.CHARSET_UTF8; } /** * Is there a Binary data upload ? * * @return Said true, false said no. */ protected boolean hasBinary() { Set<String> keys = mParamKeyValues.keySet(); for (String key : keys) { List<Object> values = mParamKeyValues.getValues(key); for (Object value : values) { if (value instanceof Binary) return true; } } return false; } ////////// static module ///////// /** * Split joint non form data. * * @param paramMap param map. * @param encodeCharset charset. * @return string parameter combination, each key value on nails with {@code "&"} space. */ public static StringBuffer buildCommonParams(MultiValueMap<String, Object> paramMap, String encodeCharset) { StringBuffer paramBuffer = new StringBuffer(); Set<String> keySet = paramMap.keySet(); for (String key : keySet) { List<Object> values = paramMap.getValues(key); for (Object value : values) { if (value != null && value instanceof CharSequence) { paramBuffer.append("&"); try { paramBuffer.append(URLEncoder.encode(key, encodeCharset)); paramBuffer.append("="); paramBuffer.append(URLEncoder.encode(value.toString(), encodeCharset)); } catch (UnsupportedEncodingException e) { Logger.e("Encoding " + encodeCharset + " format is not supported by the system"); paramBuffer.append(key); paramBuffer.append("="); paramBuffer.append(value.toString()); } } } } if (paramBuffer.length() > 0) paramBuffer.deleteCharAt(0); return paramBuffer; } /** * Accept-Language. */ private static String acceptLanguage; /** * Create acceptLanguage. * * @return Returns the client can accept the language types. Such as:zh-CN,zh. */ public static String defaultAcceptLanguage() { if (TextUtils.isEmpty(acceptLanguage)) { Locale locale = Locale.getDefault(); String language = locale.getLanguage(); String country = locale.getCountry(); StringBuilder acceptLanguageBuilder = new StringBuilder(language); if (!TextUtils.isEmpty(country)) acceptLanguageBuilder.append('-').append(country).append(',').append(language); acceptLanguage = acceptLanguageBuilder.toString(); } return acceptLanguage; } /** * Randomly generated boundary mark. * * @return Random code. */ public static String createBoundary() { StringBuffer sb = new StringBuffer("------------------"); for (int t = 1; t < 12; t++) { long time = System.currentTimeMillis() + t; if (time % 3L == 0L) { sb.append((char) (int) time % '\t'); } else if (time % 3L == 1L) { sb.append((char) (int) (65L + time % 26L)); } else { sb.append((char) (int) (97L + time % 26L)); } } return sb.toString(); } }