Last active
November 25, 2022 18:45
-
-
Save apatrida/6edd11a7d6f69a54bd66 to your computer and use it in GitHub Desktop.
Vert-x3 wrappers for HttpServletRequest and HttpServletResponse
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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('/')}" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.