Skip to content

Instantly share code, notes, and snippets.

@apatrida
Last active November 25, 2022 18:45
Show Gist options
  • Save apatrida/6edd11a7d6f69a54bd66 to your computer and use it in GitHub Desktop.
Save apatrida/6edd11a7d6f69a54bd66 to your computer and use it in GitHub Desktop.
Vert-x3 wrappers for HttpServletRequest and HttpServletResponse
package com.collokia.webapp.routes;
import io.netty.handler.codec.http.HttpHeaders;
import io.vertx.core.net.SocketAddress;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.net.URI;
import java.security.Principal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* HttpServletRequest wrapper over a vert.x {@link io.vertx.core.http.HttpServerRequest}
*/
public class VertxHttpServletRequest implements HttpServletRequest {
private final RoutingContext context;
private final URI requestUri;
private final DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public VertxHttpServletRequest(RoutingContext context) {
this.context = context;
this.requestUri = URI.create(context.request().absoluteURI());
}
@Override
public String getAuthType() {
// TODO: AUTH -- if supporting vertx-auth we would need to do something here, and other methods below (marked with TODO: AUTH)
return null;
}
@Override
public Cookie[] getCookies() {
Set<io.vertx.ext.web.Cookie> cookies = context.cookies();
Cookie[] results = new Cookie[cookies.size()];
int i = 0;
for (io.vertx.ext.web.Cookie oneCookie : cookies) {
results[i] = new Cookie(oneCookie.getName(), oneCookie.getValue());
results[i].setDomain(oneCookie.getDomain());
results[i].setPath(oneCookie.getPath());
}
return results;
}
@Override
public long getDateHeader(String name) {
String header = context.request().headers().get(name);
if (header == null) {
return -1;
}
synchronized (this) {
try {
return dateFormat.parse(header).getTime();
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
}
@Override
public String getHeader(String name) {
return context.request().headers().get(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
return Collections.enumeration(context.request().headers().getAll(name));
}
@Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(context.request().headers().names());
}
@Override
public int getIntHeader(String name) {
String header = context.request().headers().get(name);
if (header == null) {
return -1;
}
return Integer.parseInt(header);
}
@Override
public String getMethod() {
return context.request().method().toString();
}
@Override
public String getPathInfo() {
return context.request().path();
}
@Override
public String getPathTranslated() {
// TODO: is this the same as return context.normalisedPath();
throw new NotImplementedException();
}
@Override
public String getContextPath() {
// TODO: assuming we don't really mount a servlet context, root is ok
return "/";
}
@Override
public String getQueryString() {
return context.request().query();
}
@Override
public String getRemoteUser() {
// TODO: AUTH -- we don't know what type of User we have from Vert.x so can't know the name
throw new NotImplementedException();
}
@Override
public boolean isUserInRole(String role) {
// TODO: AUTH -- we could use context.user().isAuthorized(role, asyncCallback) to get the user and ask the role,
// but sometimes people prefix roles or do other things so we can't be sure how this would
return false;
}
@Override
public Principal getUserPrincipal() {
// TODO: AUTH -- would require conversion from context.user().principle() and convert it
return null;
}
@Override
public String getRequestedSessionId() {
return context.session().id();
}
@Override
public String getRequestURI() {
if (requestUri == null) {
return null;
}
return requestUri.getPath();
}
@Override
public StringBuffer getRequestURL() {
String uri = context.request().absoluteURI();
if (uri == null) {
return null;
}
int index = uri.indexOf("?");
return new StringBuffer(index >= 0 ? uri.substring(0, index) : uri);
}
@Override
public String getServletPath() {
// TODO: again, no real servlet, so this maybe could be context.currentRoute().getPath()
throw new NotImplementedException();
}
@Override
public HttpSession getSession(boolean create) {
return new WrapSession(context.session());
}
private class WrapSession implements HttpSession {
private final Session session;
WrapSession(Session session) {
this.session = session;
}
@Override
public long getCreationTime() {
throw new NotImplementedException();
}
@Override
public String getId() {
return session.id();
}
@Override
public long getLastAccessedTime() {
return session.lastAccessed();
}
@Override
public ServletContext getServletContext() {
throw new NotImplementedException();
}
@Override
public void setMaxInactiveInterval(int interval) {
throw new NotImplementedException();
}
@Override
public int getMaxInactiveInterval() {
throw new NotImplementedException();
}
@Override
public HttpSessionContext getSessionContext() {
throw new NotImplementedException();
}
@Override
public Object getAttribute(String name) {
return session.get(name);
}
@Override
public Object getValue(String name) {
return session.get(name);
}
@Override
public Enumeration<String> getAttributeNames() {
return Collections.enumeration(session.data().keySet());
}
@Override
public String[] getValueNames() {
return (String[]) session.data().keySet().toArray();
}
@Override
public void setAttribute(String name, Object value) {
if (value == null) {
session.remove(name);
} else {
session.put(name, value);
}
}
@Override
public void putValue(String name, Object value) {
if (value == null) {
session.remove(name);
} else {
session.put(name, value);
}
}
@Override
public void removeAttribute(String name) {
session.remove(name);
}
@Override
public void removeValue(String name) {
session.remove(name);
}
@Override
public void invalidate() {
session.destroy();
}
@Override
public boolean isNew() {
return false;
}
}
@Override
public HttpSession getSession() {
return new WrapSession(context.session());
}
@Override
public String changeSessionId() {
throw new NotImplementedException();
}
@Override
public boolean isRequestedSessionIdValid() {
throw new NotImplementedException();
}
@Override
public boolean isRequestedSessionIdFromCookie() {
throw new NotImplementedException();
}
@Override
public boolean isRequestedSessionIdFromURL() {
throw new NotImplementedException();
}
@Override
public boolean isRequestedSessionIdFromUrl() {
throw new NotImplementedException();
}
@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
// TODO: AUTH
throw new NotImplementedException();
}
@Override
public void login(String username, String password) throws ServletException {
// TODO: AUTH
throw new NotImplementedException();
}
@Override
public void logout() throws ServletException {
context.clearUser();
context.session().destroy();
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
throw new NotImplementedException();
}
@Override
public Part getPart(String name) throws IOException, ServletException {
throw new NotImplementedException();
}
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
throw new NotImplementedException();
}
@Override
public Object getAttribute(String name) {
return context.data().get(name);
}
@Override
public Enumeration<String> getAttributeNames() {
return Collections.enumeration(context.data().keySet());
}
@Override
public String getCharacterEncoding() {
throw new NotImplementedException();
}
@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
throw new NotImplementedException();
}
@Override
public int getContentLength() {
return getIntHeader(HttpHeaders.Names.CONTENT_LENGTH);
}
@Override
public long getContentLengthLong() {
String header = context.request().headers().get(HttpHeaders.Names.CONTENT_LENGTH);
if (header == null) {
return -1;
}
return Long.parseLong(header);
}
@Override
public String getContentType() {
return context.request().headers().get(HttpHeaders.Names.CONTENT_TYPE);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new WrappedInputStream(new ByteArrayInputStream(context.getBodyAsString().getBytes()));
}
private class WrappedInputStream extends ServletInputStream {
private final ByteArrayInputStream stream;
WrappedInputStream(ByteArrayInputStream stream) {
this.stream = stream;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return stream.available() > 0;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return stream.read();
}
}
@Override
public String getParameter(String name) {
String value = context.request().params().get(name);
if (value != null) {
return value;
}
List<String> values = context.request().formAttributes().getAll(name);
if (values != null && !values.isEmpty()) {
return values.get(0);
}
return null;
}
@Override
public Enumeration<String> getParameterNames() {
List<String> names = new ArrayList<>(context.request().params().names());
if (!context.request().formAttributes().isEmpty()) {
names.addAll(context.request().formAttributes().names());
}
return Collections.enumeration(names);
}
@Override
public String[] getParameterValues(String name) {
List<String> values = context.request().params().getAll(name);
if (!context.request().formAttributes().isEmpty()) {
List<String> formValues = context.request().formAttributes().getAll(name);
if (formValues != null && !formValues.isEmpty()) {
values.addAll(formValues);
}
}
if (values != null && !values.isEmpty()) {
return values.toArray(new String[values.size()]);
}
return EMPTY_STRING_ARRAY;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, List<String>> map = new HashMap<>();
for (Map.Entry<String, String> e : context.request().params()) {
List<String> values = map.get(e.getKey());
if (values == null) {
values = new ArrayList<>();
map.put(e.getKey(), values);
}
values.add(e.getValue());
}
for (Map.Entry<String, String> e : context.request().formAttributes().entries()) {
List<String> values = map.get(e.getKey());
if (values == null) {
values = new ArrayList<>();
map.put(e.getKey(), values);
}
values.add(e.getValue());
}
Map<String, String[]> arrayMap = new HashMap<>();
for (Map.Entry<String, List<String>> e : map.entrySet()) {
arrayMap.put(e.getKey(), e.getValue().toArray(new String[e.getValue().size()]));
}
return arrayMap;
}
@Override
public String getProtocol() {
return context.request().version().name();
}
@Override
public String getScheme() {
return requestUri.getScheme();
}
@Override
public String getServerName() {
return requestUri.getHost();
}
@Override
public int getServerPort() {
int port = requestUri.getPort();
if (port == 0) {
return ("https".equals(getScheme())) ? 443 : 80;
}
return port;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public String getRemoteAddr() {
SocketAddress address = context.request().remoteAddress();
if (address == null) {
return null;
}
return address.toString();
}
@Override
public String getRemoteHost() {
return getRemoteAddr();
}
@Override
public void setAttribute(String name, Object o) {
context.put(name, o);
}
@Override
public void removeAttribute(String name) {
context.data().remove(name);
}
@Override
public Locale getLocale() {
String header = context.request().headers().get(HttpHeaders.Names.ACCEPT_LANGUAGE);
if (header == null) {
return Locale.US;
}
return new Locale(header);
}
@Override
public Enumeration<Locale> getLocales() {
List<Locale> list = new ArrayList<>();
list.add(getLocale());
return Collections.enumeration(list);
}
@Override
public boolean isSecure() {
// TODO: would be nice if this looked at the proxy / load balancer header too. But I think servlet spec only talks about the local server itself
return getScheme().equalsIgnoreCase("https");
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
throw new NotImplementedException();
}
@Override
public String getRealPath(String path) {
throw new NotImplementedException();
}
@Override
public int getRemotePort() {
// TODO: not important
throw new NotImplementedException();
}
@Override
public String getLocalName() {
// TODO: we don't have the name handy, ip address works?
return context.request().localAddress().host();
}
@Override
public String getLocalAddr() {
return context.request().localAddress().host();
}
@Override
public int getLocalPort() {
return context.request().localAddress().port();
}
@Override
public ServletContext getServletContext() {
// TODO: doing this means we never end bleeding and implement another billion parts of servlet spec
throw new NotImplementedException();
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
throw new NotImplementedException();
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
throw new NotImplementedException();
}
@Override
public boolean isAsyncStarted() {
return false;
}
@Override
public boolean isAsyncSupported() {
return false;
}
@Override
public AsyncContext getAsyncContext() {
throw new NotImplementedException();
}
@Override
public DispatcherType getDispatcherType() {
throw new NotImplementedException();
}
}
package com.collokia.webapp.routes;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.web.RoutingContext;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
public class VertxHttpServletResponse implements HttpServletResponse {
final RoutingContext context;
private final DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public void writeToVertx() {
// The wrapper should call this when it is ready to send the response buffered here. This could be changed to have it called directly,
// but not all frameworks use the output stream in the same way, so I chose to wait until I was sure I, the wrapping code, wanted to write.
Buffer output = Buffer.buffer(buffer.toByteArray());
context.response().end(output);
}
public byte[] bufferBytes() {
return buffer.toByteArray();
}
private class WrappedOutputStream extends ServletOutputStream {
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
buffer.write(b);
}
@Override
public void flush() throws IOException {
buffer.flush();
}
@Override
public void close() throws IOException {
flush();
buffer.close();
}
}
private final WrappedOutputStream outBuffer = new WrappedOutputStream();
private final PrintWriter outWriter = new PrintWriter(outBuffer);
public VertxHttpServletResponse(RoutingContext context) {
this.context = context;
}
@Override
public void addCookie(Cookie cookie) {
throw new NotImplementedException();
}
@Override
public boolean containsHeader(String name) {
return context.response().headers().contains(name);
}
@Override
public String encodeURL(String url) {
return url; // encoding usually involves adding session information and such, but doesn't really apply to vertx
}
@Override
public String encodeRedirectURL(String url) {
return url; // encoding usually involves adding session information and such, but doesn't really apply to vertx
}
@Override
public String encodeUrl(String url) {
return url; // encoding usually involves adding session information and such, but doesn't really apply to vertx
}
@Override
public String encodeRedirectUrl(String url) {
return url; // encoding usually involves adding session information and such, but doesn't really apply to vertx
}
@Override
public void sendError(int sc, String msg) throws IOException {
context.response().setStatusCode(sc).setStatusMessage(msg).end();
}
@Override
public void sendError(int sc) throws IOException {
context.response().setStatusCode(sc).end();
}
@Override
public void sendRedirect(String location) throws IOException {
context.response().putHeader("location", location).setStatusCode(302).end();
}
@Override
public void setDateHeader(String name, long date) {
setHeader(name, dateFormat.format(new Date(date)));
}
@Override
public void addDateHeader(String name, long date) {
addHeader(name, dateFormat.format(new Date(date)));
}
@Override
public void setHeader(String name, String value) {
context.response().putHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
LinkedList<String> headers = new LinkedList<>(context.response().headers().getAll(name) );
headers.add(value);
context.response().putHeader(name,headers);
}
@Override
public void setIntHeader(String name, int value) {
setHeader(name, String.valueOf(value));
}
@Override
public void addIntHeader(String name, int value) {
addHeader(name, String.valueOf(value));
}
@Override
public void setStatus(int sc) {
context.response().setStatusCode(sc);
}
@Override
public void setStatus(int sc, String sm) {
context.response().setStatusCode(sc).setStatusMessage(sm);
}
@Override
public int getStatus() {
return context.response().getStatusCode();
}
@Override
public String getHeader(String name) {
return context.response().headers().get(name);
}
@Override
public Collection<String> getHeaders(String name) {
return context.response().headers().getAll(name);
}
@Override
public Collection<String> getHeaderNames() {
return context.response().headers().names();
}
@Override
public String getCharacterEncoding() {
throw new NotImplementedException();
}
@Override
public String getContentType() {
return getHeader(HttpHeaders.Names.CONTENT_TYPE);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return outBuffer;
}
@Override
public PrintWriter getWriter() throws IOException {
return outWriter;
}
@Override
public void setCharacterEncoding(String charset) {
throw new NotImplementedException();
}
@Override
public void setContentLength(int len) {
throw new NotImplementedException();
}
@Override
public void setContentLengthLong(long len) {
throw new NotImplementedException();
}
@Override
public void setContentType(String type) {
setHeader(HttpHeaders.Names.CONTENT_TYPE, type);
}
@Override
public void setBufferSize(int size) {
// just ignore
}
@Override
public int getBufferSize() {
// TODO: does this even matter?
return buffer.size();
}
@Override
public void flushBuffer() throws IOException {
buffer.flush();
}
@Override
public void resetBuffer() {
buffer.reset();
}
@Override
public boolean isCommitted() {
// since we defer writing, it is never committed
return false;
}
@Override
public void reset() {
context.response().setStatusCode(HttpResponseStatus.OK.code());
context.response().setStatusMessage("");
resetBuffer();
}
@Override
public void setLocale(Locale loc) {
throw new NotImplementedException();
}
@Override
public Locale getLocale() {
throw new NotImplementedException();
}
}
package org.collokia.kommon.vertx
import io.vertx.ext.auth.User
import io.vertx.ext.web.RoutingContext
import io.vertx.ext.web.Session
import nl.komponents.kovenant.deferred
import org.collokia.kommon.jdk.strings.*
import java.net.URI
public fun Session.putSafely(key: String, value: Any?) {
if (value == null) {
remove(key)
} else {
put(key, value)
}
}
public fun Session.removeSafely(key: String) {
remove<Any?>(key)
}
public fun RoutingContext.fullyQualifiedUrl(fromUrlOnThisServer: String): String {
val requestUri: URI = URI(request().absoluteURI())
val requestScheme: String = run {
request().getHeader("X-Forwarded-Proto") let { scheme: String? ->
val temp = if (scheme == null || scheme.isEmpty()) {
requestUri.getScheme()
} else {
scheme
}
temp
}
}
val requestHost: String = run {
request().getHeader("X-Forwarded-Host") let { host: String? ->
val hostWithPossiblePort = if (host == null || host.isEmpty()) {
requestUri.getHost()
} else {
host
}
hostWithPossiblePort.substringBefore(':')
}
}
val requestPort: String = run {
val rawPort = requestUri.getPort()
val tempPort = if (rawPort == 0) {
val calculated = if ("https" == requestScheme) 443 else 80
calculated
} else {
rawPort
}
request().getHeader("X-Forwarded-Port") let { port: String? ->
val tempPort = if (port == null || port.isEmpty()) {
tempPort
} else {
port
}
if (requestScheme == "https" && tempPort == "443") {
""
} else if (requestScheme == "http" && tempPort == "80") {
""
} else ":$tempPort"
}
}
return "$requestScheme://$requestHost$requestPort${fromUrlOnThisServer.mustStartWith('/')}"
}
@imdiegoruiz
Copy link

Dear Jayson Minard
I have a question....
This class VertxHttpServletRequest.java could help me to call a servlet in Java from routes of Vertx ?
Maybe you have a little example?
Thanks for your reply.

@yanickxia
Copy link

nice.... i need it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment