summaryrefslogtreecommitdiffhomepage
path: root/src/java/nginx/unit/Response.java
diff options
context:
space:
mode:
authorMax Romanov <max.romanov@gmail.com>2019-02-28 18:02:42 +0300
committerMax Romanov <max.romanov@gmail.com>2019-02-28 18:02:42 +0300
commit5bfdebb9e4161a689113d73775498949a09d7fb5 (patch)
treefcb69169e3da983db0eb1f48d2dccc2ac2ff867b /src/java/nginx/unit/Response.java
parentec7319d32c9c41597a99a9422ff324c97a92bb21 (diff)
downloadunit-5bfdebb9e4161a689113d73775498949a09d7fb5.tar.gz
unit-5bfdebb9e4161a689113d73775498949a09d7fb5.tar.bz2
Introducing Java Servlet Container beta.
Diffstat (limited to '')
-rw-r--r--src/java/nginx/unit/Response.java817
1 files changed, 817 insertions, 0 deletions
diff --git a/src/java/nginx/unit/Response.java b/src/java/nginx/unit/Response.java
new file mode 100644
index 00000000..099d7f15
--- /dev/null
+++ b/src/java/nginx/unit/Response.java
@@ -0,0 +1,817 @@
+package nginx.unit;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import java.lang.IllegalArgumentException;
+import java.lang.String;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import java.text.SimpleDateFormat;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.StringUtil;
+
+public class Response implements HttpServletResponse {
+
+ private long req_info_ptr;
+
+ private static final String defaultCharacterEncoding = "iso-8859-1";
+ private String characterEncoding = defaultCharacterEncoding;
+ private String contentType = null;
+ private String contentTypeHeader = null;
+
+ private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
+ private static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+ private static final String CONTENT_TYPE = "Content-Type";
+ private static final byte[] SET_COOKIE_BYTES = "Set-Cookie".getBytes(ISO_8859_1);
+ private static final byte[] EXPIRES_BYTES = "Expires".getBytes(ISO_8859_1);
+
+ /**
+ * The only date format permitted when generating HTTP headers.
+ */
+ public static final String RFC1123_DATE =
+ "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ private static final SimpleDateFormat format =
+ new SimpleDateFormat(RFC1123_DATE, Locale.US);
+
+ private static final String ZERO_DATE_STRING = dateToString(0);
+ private static final byte[] ZERO_DATE_BYTES = ZERO_DATE_STRING.getBytes(ISO_8859_1);
+
+ /**
+ * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
+ * will be set as HTTP ONLY.
+ */
+ public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
+
+ private OutputStream outputStream = null;
+
+ private PrintWriter writer = null;
+
+
+ public Response(long ptr) {
+ req_info_ptr = ptr;
+ }
+
+ /**
+ * Format a set cookie value by RFC6265
+ *
+ * @param name the name
+ * @param value the value
+ * @param domain the domain
+ * @param path the path
+ * @param maxAge the maximum age
+ * @param isSecure true if secure cookie
+ * @param isHttpOnly true if for http only
+ */
+ public void addSetRFC6265Cookie(
+ final String name,
+ final String value,
+ final String domain,
+ final String path,
+ final long maxAge,
+ final boolean isSecure,
+ final boolean isHttpOnly)
+ {
+ // Check arguments
+ if (name == null || name.length() == 0) {
+ throw new IllegalArgumentException("Bad cookie name");
+ }
+
+ // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+ // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules
+ //Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name");
+ // Ensure that Per RFC6265, Cookie.value follows syntax rules
+ //Syntax.requireValidRFC6265CookieValue(value);
+
+ // Format value and params
+ StringBuilder buf = new StringBuilder();
+ buf.append(name).append('=').append(value == null ? "" : value);
+
+ // Append path
+ if (path != null && path.length() > 0) {
+ buf.append(";Path=").append(path);
+ }
+
+ // Append domain
+ if (domain != null && domain.length() > 0) {
+ buf.append(";Domain=").append(domain);
+ }
+
+ // Handle max-age and/or expires
+ if (maxAge >= 0) {
+ // Always use expires
+ // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+ buf.append(";Expires=");
+ if (maxAge == 0)
+ buf.append(ZERO_DATE_STRING);
+ else
+ buf.append(dateToString(System.currentTimeMillis() + 1000L * maxAge));
+
+ buf.append(";Max-Age=");
+ buf.append(maxAge);
+ }
+
+ // add the other fields
+ if (isSecure)
+ buf.append(";Secure");
+ if (isHttpOnly)
+ buf.append(";HttpOnly");
+
+ // add the set cookie
+ addHeader(req_info_ptr, SET_COOKIE_BYTES,
+ buf.toString().getBytes(ISO_8859_1));
+
+ // Expire responses with set-cookie headers so they do not get cached.
+ setHeader(req_info_ptr, EXPIRES_BYTES, ZERO_DATE_BYTES);
+ }
+
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ trace("addCookie: " + cookie.getName() + "=" + cookie.getValue());
+
+ if (StringUtil.isBlank(cookie.getName())) {
+ throw new IllegalArgumentException("Cookie.name cannot be blank/null");
+ }
+
+ if (isCommitted()) {
+ return;
+ }
+
+ addCookie_(cookie);
+ }
+
+ private void addCookie_(Cookie cookie)
+ {
+ String comment = cookie.getComment();
+ boolean httpOnly = false;
+
+ if (comment != null && comment.contains(HTTP_ONLY_COMMENT)) {
+ httpOnly = true;
+ }
+
+ addSetRFC6265Cookie(cookie.getName(),
+ cookie.getValue(),
+ cookie.getDomain(),
+ cookie.getPath(),
+ cookie.getMaxAge(),
+ cookie.getSecure(),
+ httpOnly || cookie.isHttpOnly());
+ }
+
+ public void addSessionIdCookie(Cookie cookie)
+ {
+ trace("addSessionIdCookie: " + cookie.getName() + "=" + cookie.getValue());
+
+ if (isCommitted()) {
+ /*
+ 9.3 The Include Method
+
+ ... any call to HttpServletRequest.getSession() or
+ HttpServletRequest.getSession(boolean) that would require
+ adding a Cookie response header must throw an
+ IllegalStateException if the response has been committed.
+ */
+ throw new IllegalStateException("Response already sent");
+ }
+
+ addCookie_(cookie);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ trace("addDateHeader: " + name + ": " + date);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ String value = dateToString(date);
+
+ addHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+ private static String dateToString(long date)
+ {
+ Date dateValue = new Date(date);
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return format.format(dateValue);
+ }
+
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ trace("addHeader: " + name + ": " + value);
+
+ if (value == null) {
+ return;
+ }
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (CONTENT_TYPE.equalsIgnoreCase(name)) {
+ setContentType(value);
+ return;
+ }
+
+ addHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+ private static native void addHeader(long req_info_ptr, byte[] name, byte[] value);
+
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ trace("addIntHeader: " + name + ": " + value);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ addIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value);
+ }
+
+ private static native void addIntHeader(long req_info_ptr, byte[] name, int value);
+
+
+ @Override
+ public boolean containsHeader(String name)
+ {
+ trace("containsHeader: " + name);
+
+ return containsHeader(req_info_ptr, name.getBytes(ISO_8859_1));
+ }
+
+ private static native boolean containsHeader(long req_info_ptr, byte[] name);
+
+
+ @Override
+ @Deprecated
+ public String encodeRedirectUrl(String url)
+ {
+ return encodeRedirectURL(url);
+ }
+
+ @Override
+ public String encodeRedirectURL(String url)
+ {
+ log("encodeRedirectURL: " + url);
+
+ return url;
+ }
+
+ @Override
+ @Deprecated
+ public String encodeUrl(String url)
+ {
+ return encodeURL(url);
+ }
+
+ @Override
+ public String encodeURL(String url)
+ {
+ log("encodeURL: " + url);
+
+ return url;
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ trace("getHeader: " + name);
+
+ return getHeader(req_info_ptr, name.getBytes(ISO_8859_1));
+ }
+
+ private static native String getHeader(long req_info_ptr, byte[] name);
+
+
+ @Override
+ public Collection<String> getHeaderNames()
+ {
+ trace("getHeaderNames");
+
+ Enumeration<String> e = getHeaderNames(req_info_ptr);
+ if (e == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.list(e);
+ }
+
+ private static native Enumeration<String> getHeaderNames(long req_info_ptr);
+
+
+ @Override
+ public Collection<String> getHeaders(String name)
+ {
+ trace("getHeaders: " + name);
+
+ Enumeration<String> e = getHeaders(req_info_ptr, name.getBytes(ISO_8859_1));
+ if (e == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.list(e);
+ }
+
+ private static native Enumeration<String> getHeaders(long req_info_ptr, byte[] name);
+
+
+ @Override
+ public int getStatus()
+ {
+ trace("getStatus");
+
+ return getStatus(req_info_ptr);
+ }
+
+ private static native int getStatus(long req_info_ptr);
+
+
+ @Override
+ public void sendError(int sc) throws IOException
+ {
+ sendError(sc, null);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException
+ {
+ trace("sendError: " + sc + ", " + msg);
+
+ if (isCommitted()) {
+ throw new IllegalStateException("Response already sent");
+ }
+
+ setStatus(sc);
+
+ Request request = getRequest(req_info_ptr);
+
+ // If we are allowed to have a body, then produce the error page.
+ if (sc != SC_NO_CONTENT && sc != SC_NOT_MODIFIED &&
+ sc != SC_PARTIAL_CONTENT && sc >= SC_OK)
+ {
+ request.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, sc);
+ request.setAttribute_(RequestDispatcher.ERROR_MESSAGE, msg);
+ request.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI,
+ request.getRequestURI());
+/*
+ request.setAttribute_(RequestDispatcher.ERROR_SERVLET_NAME,
+ request.getServletName());
+*/
+ }
+
+/*
+ Avoid commit and give chance for error handlers.
+
+ if (!request.isAsyncStarted()) {
+ commit();
+ }
+*/
+ }
+
+ private static native Request getRequest(long req_info_ptr);
+
+ private void commit()
+ {
+ if (writer != null) {
+ writer.close();
+
+ } else if (outputStream != null) {
+ outputStream.close();
+
+ } else {
+ commit(req_info_ptr);
+ }
+ }
+
+ private static native void commit(long req_info_ptr);
+
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ trace("sendRedirect: " + location);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ try {
+ URI uri = new URI(location);
+
+ if (!uri.isAbsolute()) {
+ URI req_uri = new URI(getRequest(req_info_ptr).getRequestURL().toString());
+ uri = req_uri.resolve(uri);
+
+ location = uri.toString();
+ }
+ } catch (URISyntaxException e) {
+ log("sendRedirect: failed to send redirect: " + e);
+ return;
+ }
+
+ sendRedirect(req_info_ptr, location.getBytes(ISO_8859_1));
+ }
+
+ private static native void sendRedirect(long req_info_ptr, byte[] location);
+
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ trace("setDateHeader: " + name + ": " + date);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ String value = dateToString(date);
+
+ setHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ trace("setHeader: " + name + ": " + value);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (CONTENT_TYPE.equalsIgnoreCase(name)) {
+ setContentType(value);
+ return;
+ }
+
+ /*
+ * When value is null container behaviour is undefined.
+ * - Tomcat ignores setHeader call;
+ * - Jetty & Resin acts as removeHeader;
+ */
+ if (value == null) {
+ removeHeader(req_info_ptr, name.getBytes(ISO_8859_1));
+ return;
+ }
+
+ setHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+ private static native void setHeader(long req_info_ptr, byte[] name, byte[] value);
+
+ private static native void removeHeader(long req_info_ptr, byte[] name);
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ trace("setIntHeader: " + name + ": " + value);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value);
+ }
+
+ private static native void setIntHeader(long req_info_ptr, byte[] name, int value);
+
+
+ @Override
+ public void setStatus(int sc)
+ {
+ trace("setStatus: " + sc);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setStatus(req_info_ptr, sc);
+ }
+
+ private static native void setStatus(long req_info_ptr, int sc);
+
+
+ @Override
+ @Deprecated
+ public void setStatus(int sc, String sm)
+ {
+ trace("setStatus: " + sc + "; " + sm);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setStatus(req_info_ptr, sc);
+ }
+
+
+ @Override
+ public void flushBuffer() throws IOException
+ {
+ trace("flushBuffer");
+
+ if (writer != null) {
+ writer.flush();
+ }
+
+ if (outputStream != null) {
+ outputStream.flush();
+ }
+ }
+
+ @Override
+ public int getBufferSize()
+ {
+ trace("getBufferSize");
+
+ return getBufferSize(req_info_ptr);
+ }
+
+ public static native int getBufferSize(long req_info_ptr);
+
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ trace("getCharacterEncoding");
+
+ return characterEncoding;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ /* In JIRA decorator get content type called after commit. */
+
+ String res = contentTypeHeader;
+
+ trace("getContentType: " + res);
+
+ return res;
+ }
+
+ private static native String getContentType(long req_info_ptr);
+
+ @Override
+ public Locale getLocale()
+ {
+ log("getLocale");
+
+ return null;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ trace("getOutputStream");
+
+ if (writer != null) {
+ throw new IllegalStateException("Writer already created");
+ }
+
+ if (outputStream == null) {
+ outputStream = new OutputStream(req_info_ptr);
+ }
+
+ return outputStream;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException
+ {
+ trace("getWriter ( characterEncoding = '" + characterEncoding + "' )");
+
+ if (outputStream != null) {
+ throw new IllegalStateException("OutputStream already created");
+ }
+
+ if (writer == null) {
+ ServletOutputStream stream = new OutputStream(req_info_ptr);
+
+ writer = new PrintWriter(
+ new OutputStreamWriter(stream, Charset.forName(characterEncoding)),
+ false);
+ }
+
+ return writer;
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ trace("isCommitted");
+
+ return isCommitted(req_info_ptr);
+ }
+
+ public static native boolean isCommitted(long req_info_ptr);
+
+ @Override
+ public void reset()
+ {
+ trace("reset");
+
+ if (isCommitted()) {
+ return;
+ }
+
+ reset(req_info_ptr);
+
+ writer = null;
+ outputStream = null;
+ }
+
+ public static native void reset(long req_info_ptr);
+
+ @Override
+ public void resetBuffer()
+ {
+ trace("resetBuffer");
+
+ resetBuffer(req_info_ptr);
+
+ writer = null;
+ outputStream = null;
+ }
+
+ public static native void resetBuffer(long req_info_ptr);
+
+ @Override
+ public void setBufferSize(int size)
+ {
+ trace("setBufferSize: " + size);
+
+ setBufferSize(req_info_ptr, size);
+ }
+
+ public static native void setBufferSize(long req_info_ptr, int size);
+
+ @Override
+ public void setCharacterEncoding(String charset)
+ {
+ trace("setCharacterEncoding " + charset);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (charset == null) {
+ if (writer != null
+ && !characterEncoding.equalsIgnoreCase(defaultCharacterEncoding))
+ {
+ /* TODO throw */
+ return;
+ }
+
+ characterEncoding = defaultCharacterEncoding;
+ } else {
+ if (writer != null
+ && !characterEncoding.equalsIgnoreCase(charset))
+ {
+ /* TODO throw */
+ return;
+ }
+
+ characterEncoding = charset;
+ }
+
+ if (contentType != null) {
+ String type = contentType + ";charset=" + characterEncoding;
+
+ contentTypeHeader = type;
+
+ setContentType(req_info_ptr, type.getBytes(ISO_8859_1));
+ }
+ }
+
+
+ @Override
+ public void setContentLength(int len)
+ {
+ trace("setContentLength: " + len);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setContentLength(req_info_ptr, len);
+ }
+
+ @Override
+ public void setContentLengthLong(long len)
+ {
+ trace("setContentLengthLong: " + len);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setContentLength(req_info_ptr, len);
+ }
+
+ private static native void setContentLength(long req_info_ptr, long len);
+
+
+ @Override
+ public void setContentType(String type)
+ {
+ trace("setContentType: " + type);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (type == null) {
+ removeContentType(req_info_ptr);
+ contentType = null;
+ contentTypeHeader = null;
+ return;
+ }
+
+ String charset = MimeTypes.getCharsetFromContentType(type);
+ String ctype = MimeTypes.getContentTypeWithoutCharset(type);
+
+ if (writer != null
+ && charset != null
+ && !characterEncoding.equalsIgnoreCase(charset))
+ {
+ /* To late to change character encoding */
+ charset = characterEncoding;
+ type = ctype + ";charset=" + characterEncoding;
+ }
+
+ if (charset == null) {
+ type = type + ";charset=" + characterEncoding;
+ } else {
+ characterEncoding = charset;
+ }
+
+ contentType = ctype;
+ contentTypeHeader = type;
+
+ setContentType(req_info_ptr, type.getBytes(ISO_8859_1));
+ }
+
+ private static native void setContentType(long req_info_ptr, byte[] type);
+
+ private static native void removeContentType(long req_info_ptr);
+
+
+ @Override
+ public void setLocale(Locale loc)
+ {
+ log("setLocale: " + loc);
+ }
+
+ private void log(String msg)
+ {
+ msg = "Response." + msg;
+ log(req_info_ptr, msg.getBytes(UTF_8));
+ }
+
+ public static native void log(long req_info_ptr, byte[] msg);
+
+
+ private void trace(String msg)
+ {
+ msg = "Response." + msg;
+ trace(req_info_ptr, msg.getBytes(UTF_8));
+ }
+
+ public static native void trace(long req_info_ptr, byte[] msg);
+}