Java tutorial
/* * Copyright 2014 Brian Ethier * * 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.barenode.bareconnection; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import com.google.gson.stream.JsonReader; public class RestConnection { public static final String METHOD_OPTIONS = "OPTIONS"; public static final String METHOD_GET = "GET"; public static final String METHOD_HEAD = "HEAD"; public static final String METHOD_POST = "POST"; public static final String METHOD_PUT = "PUT"; public static final String METHOD_DELETE = "DELETE"; public static final String METHOD_TRACE = "TRACE"; public static final String METHOD_CONNECT = "CONNECT"; public static final int SC_UNKNOWN = -1; public static final int SC_OK = 200; public static final int SC_CREATED = 201; public static final int SC_ACCEPTED = 202; public static final int SC_NOT_AUTHORITATIVE = 203; public static final int SC_NO_CONTENT = 204; public static final int SC_RESET = 205; public static final int SC_PARTIAL = 206; public static final int SC_MULT_CHOICE = 300; public static final int SC_MOVED_PERM = 301; public static final int SC_MOVED_TEMP = 302; public static final int SC_SEE_OTHER = 303; public static final int SC_NOT_MODIFIED = 304; public static final int SC_USE_PROXY = 305; public static final int SC_BAD_REQUEST = 400; public static final int SC_UNAUTHORIZED = 401; public static final int SC_PAYMENT_REQUIRED = 402; public static final int SC_FORBIDDEN = 403; public static final int SC_NOT_FOUND = 404; public static final int SC_BAD_METHOD = 405; public static final int SC_NOT_ACCEPTABLE = 406; public static final int SC_PROXY_AUTH = 407; public static final int SC_CLIENT_TIMEOUT = 408; public static final int SC_CONFLICT = 409; public static final int SC_GONE = 410; public static final int SC_LENGTH_REQUIRED = 411; public static final int SC_PRECON_FAILED = 412; public static final int SC_ENTITY_TOO_LARGE = 413; public static final int SC_REQ_TOO_LONG = 414; public static final int SC_UNSUPPORTED_TYPE = 415; public static final int SC_INTERNAL_ERROR = 500; public static final int SC_NOT_IMPLEMENTED = 501; public static final int SC_BAD_GATEWAY = 502; public static final int SC_UNAVAILABLE = 503; public static final int SC_GATEWAY_TIMEOUT = 504; public static final int SC_VERSION = 505; public static final String HEADER_CONTENT_TYPE = "Content-Type"; public static final String HEADER_SET_COOKIE = "Set-Cookie"; public static final String HEADER_COOKIE = "Cookie"; public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String KEY_CHARSET = "charset"; public static final String KEY_BOUNDARY = "boundary"; public static final String AUTHORIZATION_TYPE_BASIC = "Basic"; public static final String CONTENT_TYPE_TEXT = "text/plain"; public static final String CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"; public static final String CONTENT_TYPE_JSON = "application/json"; public static final String CONTENT_TYPE_MULTIPART_FORM = "multipart/form-data"; public static final String PATH_SEPARATOR = "/"; public static final String QUERY_SEPARATOR = "?"; public static final String DEFAULT_CHARSET = "UTF-8"; public static final int DEFAULT_CONNECT_TIMEOUT = 1000; public static final int DEFAULT_SOCKET_TIMEOUT = 10000; public interface MultipartFormWriter { public void onWrite(OutputStream out, String charset, String boundary) throws IOException; } private final HttpURLConnection mConnection; private String mContentType = CONTENT_TYPE_JSON; private String mIncomingCharset = DEFAULT_CHARSET; private String mOutgoingCharset = DEFAULT_CHARSET; private int mResponseCode = SC_UNKNOWN; public RestConnection(HttpURLConnection connection) { mConnection = connection; } public HttpURLConnection getConnection() { return mConnection; } public void head() throws RestException { ensureNewConnection(); try { mConnection.setRequestMethod(METHOD_HEAD); checkResponseCode(); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { mConnection.disconnect(); } } public <T> T get(Class<T> clss) throws RestException { ensureNewConnection(); try { mConnection.setRequestMethod(METHOD_GET); checkResponseCode(); return read(mConnection.getInputStream(), clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public <T> List<T> getList(Class<T> clss) throws RestException { ensureNewConnection(); try { mConnection.setRequestMethod(METHOD_GET); checkResponseCode(); return RestUtils.fromJsonToList(mConnection.getInputStream(), mIncomingCharset, clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public void put() throws RestException { put(String.class, null); } public <T> T put(Class<T> clss, Object object) throws RestException { ensureNewConnection(); try { String contentType = mContentType + ";" + KEY_CHARSET + "=" + mOutgoingCharset; mConnection.setRequestMethod(METHOD_PUT); mConnection.setRequestProperty(HEADER_CONTENT_TYPE, contentType); mConnection.setDoOutput(true); write(mConnection.getOutputStream(), object); checkResponseCode(); return read(mConnection.getInputStream(), clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public <T> T post(Class<T> clss, Object object) throws RestException { ensureNewConnection(); try { String contentType = mContentType + ";" + KEY_CHARSET + "=" + mOutgoingCharset; mConnection.setRequestMethod(METHOD_POST); mConnection.setRequestProperty(HEADER_CONTENT_TYPE, contentType); mConnection.setDoOutput(true); write(mConnection.getOutputStream(), object); checkResponseCode(); return read(mConnection.getInputStream(), clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public <T> T post(Class<T> clss, HashMap<String, String> params) throws RestException { ensureNewConnection(); try { String query = RestUtils.buildQuery(params, mOutgoingCharset); String contentType = CONTENT_TYPE_FORM_URLENCODED + ";" + KEY_CHARSET + "=" + mOutgoingCharset; mConnection.setRequestMethod(METHOD_POST); mConnection.setRequestProperty(HEADER_CONTENT_TYPE, contentType); mConnection.setDoOutput(true); write(mConnection.getOutputStream(), query); checkResponseCode(); return read(mConnection.getInputStream(), clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public <T> T post(Class<T> clss, List<Entity> entities) throws RestException { return post(clss, new DefaultMultipartFormWriter(entities)); } public <T> T post(Class<T> clss, MultipartFormWriter writer) throws RestException { ensureNewConnection(); try { String boundary = Long.toHexString(System.currentTimeMillis()); String contentType = CONTENT_TYPE_MULTIPART_FORM + ";" + KEY_BOUNDARY + "=" + boundary; mConnection.setRequestMethod(METHOD_POST); mConnection.setRequestProperty(HEADER_CONTENT_TYPE, contentType); mConnection.setDoOutput(true); OutputStream out = mConnection.getOutputStream(); writer.onWrite(out, mOutgoingCharset, boundary); out.flush(); checkResponseCode(); return read(mConnection.getInputStream(), clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public <T> T delete(Class<T> clss) throws RestException { ensureNewConnection(); try { mConnection.setRequestMethod(METHOD_DELETE); checkResponseCode(); return read(mConnection.getInputStream(), clss); } catch (IOException e) { throw new RestException(mResponseCode, e); } finally { if (!isResponseStreaming(clss)) { mConnection.disconnect(); } } } public List<String> getCookies() { if (mResponseCode == SC_UNKNOWN) { throw new IllegalStateException( "A connection to the server needs to be made before retrieving cookies."); } ArrayList<String> cookies = new ArrayList<String>(); List<String> incomingCookies = mConnection.getHeaderFields().get(HEADER_SET_COOKIE); for (String cookie : incomingCookies) { cookies.add(cookie.split(";", 2)[0]); } return cookies; } public int getResponseCode() { return mResponseCode; } public void disconnect() { mConnection.disconnect(); } private void ensureNewConnection() { if (mResponseCode != SC_UNKNOWN) { throw new IllegalStateException("A RestConnection could only be used once!"); } } private void checkResponseCode() throws RestException { try { mResponseCode = mConnection.getResponseCode(); if (mResponseCode / 100 != 2) { String responseError = RestUtils.readString(mConnection.getErrorStream(), mIncomingCharset); throw new RestException(mResponseCode, responseError); } updateIncomingCharset(); } catch (IOException e) { throw new RestException(mResponseCode, e); } } private void updateIncomingCharset() { String contentType = mConnection.getHeaderField(HEADER_CONTENT_TYPE); for (String param : contentType.replace(" ", "").split(";")) { if (param.startsWith(KEY_CHARSET + "=")) { mIncomingCharset = param.split("=", 2)[1]; break; } } } private void setContentType(String contentType) { mContentType = contentType; } private void setIncomingCharset(String charset) { mIncomingCharset = charset; } private void setOutgoingCharset(String charset) { mOutgoingCharset = charset; } private boolean isResponseStreaming(Class<?> clss) { if (clss.isAssignableFrom(InputStream.class)) { return true; } if (clss.isAssignableFrom(JsonReader.class)) { return true; } return false; } @SuppressWarnings("unchecked") private <T> T read(InputStream in, Class<T> clss) throws IOException { if (clss.isAssignableFrom(InputStream.class)) { return (T) in; } else if (clss.isAssignableFrom(JsonReader.class)) { return (T) new JsonReader(new InputStreamReader(in, mIncomingCharset)); } else if (clss.isAssignableFrom(String.class)) { return (T) RestUtils.readString(in, mIncomingCharset); } else { return RestUtils.fromJson(in, mIncomingCharset, clss); } } private void write(OutputStream out, Object object) throws IOException { if (object instanceof InputStream) { RestUtils.copy((InputStream) object, out); out.flush(); out.close(); } else if (object instanceof String) { String data = (String) object; out.write(data.getBytes(mOutgoingCharset)); out.flush(); out.close(); } else if (object != null) { String json = RestUtils.toJson(object); out.write(json.getBytes(mOutgoingCharset)); out.flush(); out.close(); } } public static final class Builder { private RestProperties mProperties = new RestProperties(); private String mAuthorizationType = AUTHORIZATION_TYPE_BASIC; private String mContentType = CONTENT_TYPE_JSON; private String mIncomingCharset = DEFAULT_CHARSET; private String mOutgoingCharset = DEFAULT_CHARSET; private HashMap<String, String> mParams; private List<String> mCookies; public Builder url(String url) { mProperties.url(url); return this; } public Builder path(String path) { mProperties.path(path); return this; } public Builder username(String username) { mProperties.username(username); return this; } public Builder password(String password) { mProperties.password(password); return this; } public Builder connectTimeout(int connectTimeout) { mProperties.connectTimeout(connectTimeout); return this; } public Builder readTimeout(int readTimeout) { mProperties.readTimeout(readTimeout); return this; } public Builder properties(RestProperties properties) { mProperties = properties == null ? new RestProperties() : properties; return this; } public Builder authorizationType(String authorizationType) { mAuthorizationType = authorizationType; return this; } public Builder contentType(String contentType) { mContentType = contentType; return this; } public Builder incomingCharset(String charset) { mIncomingCharset = charset; return this; } public Builder outgoingCharset(String charset) { mOutgoingCharset = charset; return this; } public Builder params(HashMap<String, String> params) { mParams = params; return this; } public Builder cookies(List<String> cookies) { mCookies = cookies; return this; } public RestConnection build() throws RestException { try { if (mProperties.getUrl() == null || mProperties.getUrl().isEmpty()) { throw new MalformedURLException("You must call url(...) with a valid URL value!"); } StringBuilder url = new StringBuilder(mProperties.getUrl()); if (mProperties.getPath() != null && !mProperties.getPath().isEmpty()) { url.append(PATH_SEPARATOR); url.append(mProperties.getPath()); } if (mParams != null && !mParams.isEmpty()) { url.append(QUERY_SEPARATOR); url.append(RestUtils.buildQuery(mParams, mOutgoingCharset)); } HttpURLConnection connection = (HttpURLConnection) new URL(url.toString()).openConnection(); connection.setConnectTimeout(mProperties.getConnectTimeout()); connection.setReadTimeout(mProperties.getReadTimeout()); connection.setRequestProperty(HEADER_ACCEPT_CHARSET, mOutgoingCharset); if (mProperties.getUsername() != null && mProperties.getPassword() != null) { String credentials = mProperties.getUsername() + ":" + mProperties.getPassword(); String authorization = mAuthorizationType + " " + Base64.encodeBytes(credentials.getBytes()); connection.setRequestProperty(HEADER_AUTHORIZATION, authorization); } if (mCookies != null) { for (String cookie : mCookies) { connection.addRequestProperty(HEADER_COOKIE, cookie); } } RestConnection restConnection = new RestConnection(connection); restConnection.setContentType(mContentType); restConnection.setIncomingCharset(mIncomingCharset); restConnection.setOutgoingCharset(mOutgoingCharset); return restConnection; } catch (IOException e) { throw new RestException(SC_UNKNOWN, e); } } } }