package io.myweb.http; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; import org.json.JSONException; import org.json.JSONObject; public class Request { public static final int BUFFER_LENGTH = 32 * 1024; public static final int IN_MEMORY_LIMIT = 2 * 1024 * 1024; // 2 MB private final Method method; private URI uri; private final String protocolVersion; private final Headers headers; private final Cookies cookies; private String stringBody; private JSONObject jsonBody; private byte[] cachedBody; private File fileBody; private InputStream body; private Map<String,String> parameterMap = null; private long contentLength = -1; private boolean redirected = false; public Request(String url) { this(Method.GET, URI.create(url), "1.1", new Headers()); } private Request(Method method, URI uri, String protocolVersion, Headers headers) { this.method = method; this.uri = uri; this.protocolVersion = protocolVersion; this.headers = headers; this.cookies = Cookies.parse(headers); } public boolean isCached() { return (cachedBody != null); } public long getContentLenght() { if (contentLength < 0 && headers != null) { String lenStr = headers.get(Headers.REQUEST.CONTENT_LEN); if (lenStr != null) contentLength = Long.parseLong(lenStr); } return contentLength; } public boolean isKeptAlive() { Headers.Header keepAliveHeader = getHeaders().findFirst(Headers.REQUEST.CONNECTION); return keepAliveHeader!=null && "keep-alive".equalsIgnoreCase(keepAliveHeader.getValue()); } public Method getMethod() { return method; } public URI getURI() { return uri; } public String getProtocolVersion() { return protocolVersion; } public Headers getHeaders() { return headers; } public Cookies getCookies() { return cookies; } public InputStream getBody() { if (cachedBody != null) { return new ByteArrayInputStream(cachedBody); } // As for now make sure we will give out body as InputStream only once // TODO create filter for input stream to monitor how many bytes has been read InputStream is = body; body = null; return is; } public String getBodyAsString() { if (stringBody != null) return stringBody; if (jsonBody != null) { stringBody = jsonBody.toString(); } else { if (cachedBody != null) stringBody = new String(cachedBody); } return stringBody; } public JSONObject getBodyAsJSON() { if (jsonBody != null) return jsonBody; try { if (stringBody != null) { jsonBody = new JSONObject(stringBody); } else { if (cachedBody != null) jsonBody = new JSONObject(getBodyAsString()); } } catch (JSONException ex) { ex.printStackTrace(); } return jsonBody; } public String getId() { Headers.Header myHeader = getHeaders().findFirst(Headers.X.MYWEB_ID); if (myHeader!=null) return myHeader.getValue(); return null; } public Request withBody(InputStream is) throws IOException { // always read body if (getContentLenght() > 0) { body = is; if (getContentLenght() <= IN_MEMORY_LIMIT) { cachedBody = new byte[(int) getContentLenght()]; readBody(cachedBody); } body = null; // TODO think how to handle chunked requests } return this; } public Request withBody(String s) { stringBody = s; return this; } public Request withBody(JSONObject json) { jsonBody = json; return this; } public Request withId(String id) { if (id != null) getHeaders().update(Headers.X.MYWEB_ID, id); return this; } public Request withRedirection(URI u) { if (u != null) { getHeaders().update(Headers.REQUEST.ORIGIN, uri.toString()); redirected = true; uri = u; } else { redirected = false; } return this; } public boolean isRedirected() { return redirected; } public Map<String, String> getParameterMap() { if (parameterMap == null) { if (Method.POST.equals(method)) { parameterMap = decodeQueryString(getBodyAsString()); } else { parameterMap = decodeQueryString(queryParams(uri.toString())); } } return parameterMap; } private static Map<String, String> decodeQueryString(String queryParamsStr) { if (queryParamsStr == null) { return new HashMap<String, String>(); } String[] nameAndValues = queryParamsStr.split("&"); Map<String, String> result = new HashMap<String, String>(); for (String nameAndVal : nameAndValues) { if (!"".equals(nameAndVal)) { int idx = nameAndVal.indexOf("="); try { if(idx>0) result.put(nameAndVal.substring(0,idx), URLDecoder.decode(nameAndVal.substring(idx + 1), "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } return result; } private static String queryParams(String url) { int i = url.indexOf("?"); String queryParams; if (i == -1) { queryParams = ""; } else { queryParams = url.substring(i + 1); } return queryParams; } public static Request parse(String req) throws HttpBadRequestException { if (req == null || req.length() == 0) return null; String[] lines = req.split("\\r\\n", 2); String[] segments = lines[0].split(" "); Method method = Method.findByName(segments[0]); if (method == null) throw new HttpBadRequestException("Invalid HTTP method: " + segments[0]); URI uri = URI.create(segments[1]); String protocolVersion = segments[2]; Headers headers = Headers.parse(lines[1]); return new Request(method, uri, protocolVersion, headers); } public long readBody() throws IOException { return readBody(null); } public long readBody(final byte[] target) throws IOException { long len = getContentLenght(); if (target != null && target.length < len) len = target.length; return readBody(target, len); } public long readBody(final byte[] target, long maxLength) throws IOException { if (maxLength <= 0 || body == null) return -1; long totalRead = 0; while (totalRead < maxLength) { long bytesToRead = maxLength - totalRead; int len = BUFFER_LENGTH; if (len > bytesToRead) len = (int) bytesToRead; if (target != null) { int bytesRead = body.read(target, (int) totalRead, len); if (bytesRead < 0) break; totalRead += bytesRead; } else { long bytesSkipped = body.skip(maxLength); totalRead += bytesSkipped; } } return totalRead; } }