How do I serve https and http for Jetty from one port?
(I know it's a duplicate question but the original poster asked it for the wrong reason. I'm not implying that I'm asking it for the right reason, but let's see.)
We have a web service which runs on a non-standard port number. Even though users seem to be able to remember the port number, occasionally they type http: instead of https: by mistake. Someone is asking whether we can serve HTTP on that port and then redirect them to HTTPS on the same port. It sounds evil... I like the usability but it feels like maybe it should be the browser's job to do this?
The one solution I have seen was "write your own proxy in front of Jetty." This solution would work, but I don't think it would work well as I am not confident that I can write a proxy which is as efficient as Jetty itself. Plus, even if the proxy itself is efficient, all the data would still have to go an additional hop, which is guaranteed to slow down the traffic anyway.
Is there a better way than this? Perhaps Jetty itself has some place where the protocol detection logic could be wedged which would allow taking advantage of their speed while also removing the additional hop a proxy would introduce.
Solution 1:
Update: See this answer for instructions on how to redirect a single port to both an HTTPS and HTTP listener. If for whatever reason you don't use that solution, see below:
It isn't possible to pipe traffic from both http and https on the same port. Jetty uses two completely different connectors to bind to the secure and unsecure ports. In fact, every web server I've encountered binds the two protocols to two completely separate ports.
One thing I would suggest for usability's sake is to use default ports, which completely hides the port from the user. By default http uses port 80, and by default https uses port 443. So if you configure your connectors to run on port 80 and port 443 respectively, then your users don't have to type a port, and your development team doesn't have to handle including port numbers in absolute paths in HTML, CSS, JavaScript, and other resources.
Jetty is designed to be a standalone Web server, unlike older versions of Tomcat, which Apache suggests run behind the Apache HTTP server. Therefore, as long as you have no other HTTP server running, and using those ports so you cannot, you should be able to configure Jetty to run on the default ports without any problem. This comes from experience. We run Jetty precisely in this manner.
Finally, a protocol can be bound to more than one port. Thus, if you're currently running Jetty on ports 8080 for http and 8443 for https, you can leave those connectors active and add two more connectors for port 80 and port 443. This enabled backwards compatibility for the part of your app that is still using the port numbers and gives you time to walk this forward.
<!-- Legacy HTTP connector -->
<Call name="addConnector">
<Arg>
<New class="org.mortbay.jetty.nio.SelectChannelConnector">
<Set name="host"><SystemProperty name="jetty.host" /></Set>
<Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
<Set name="maxIdleTime">30000</Set>
<Set name="Acceptors">2</Set>
<Set name="statsOn">false</Set>
<Set name="confidentialPort">8443</Set>
<Set name="lowResourcesConnections">5000</Set>
<Set name="lowResourcesMaxIdleTime">5000</Set>
</New>
</Arg>
</Call>
<!-- Second connector for http on port 80 -->
<Call name="addConnector">
<Arg>
<New class="org.mortbay.jetty.nio.SelectChannelConnector">
<Set name="host"><SystemProperty name="jetty.host" /></Set>
<Set name="port"><SystemProperty name="jetty.port" default="80"/></Set>
<Set name="maxIdleTime">30000</Set>
<Set name="Acceptors">2</Set>
<Set name="statsOn">false</Set>
<Set name="confidentialPort">8443</Set>
<Set name="lowResourcesConnections">5000</Set>
<Set name="lowResourcesMaxIdleTime">5000</Set>
</New>
</Arg>
</Call>
<!-- Legacy SSL Connector for https port 8443 -->
<Call name="addConnector">
<Arg>
<New class="org.mortbay.jetty.security.SslSocketConnector">
<Set name="Port">8443</Set>
<Set name="maxIdleTime">30000</Set>
<Set name="handshakeTimeout">2000</Set>
<Set name="keystore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
<Set name="password">xxxxxx</Set>
<Set name="keyPassword">xxxxxx</Set>
<Set name="truststore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
<Set name="trustPassword">OBF:xxxxx</Set>
<Set name="handshakeTimeout">2000</Set>
<!-- Set name="ThreadPool">
<New class="org.mortbay.thread.BoundedThreadPool">
<Set name="minThreads">10</Set>
<Set name="maxThreads">250</Set>
</New>
</Set -->
</New>
</Arg>
</Call>
<!-- Default SSL Connector for https port 443 -->
<Call name="addConnector">
<Arg>
<New class="org.mortbay.jetty.security.SslSocketConnector">
<Set name="Port">443</Set>
<Set name="maxIdleTime">30000</Set>
<Set name="handshakeTimeout">2000</Set>
<Set name="keystore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
<Set name="password">xxxxxx</Set>
<Set name="keyPassword">xxxxxx</Set>
<Set name="truststore"><SystemProperty name="jetty.home" default="." />/etc/keystore</Set>
<Set name="trustPassword">OBF:xxxxx</Set>
<Set name="handshakeTimeout">2000</Set>
<!-- Set name="ThreadPool">
<New class="org.mortbay.thread.BoundedThreadPool">
<Set name="minThreads">10</Set>
<Set name="maxThreads">250</Set>
</New>
</Set -->
</New>
</Arg>
</Call>
For the 2nd and 4th connectors, the only real differences are the port numbers. In short, you can configure multiple ports per connector/protocol, but you cannot configure multiple protocols/connectors for the same port.
Solution 2:
Update: As of jetty-9.4.15.v20190215 support for port unification is built into Jetty; see this answer.
Yes We Can
This is possible and we have done it. The code here works with Jetty 8; I have not tested with Jetty 9, but this answer has similar code for Jetty 9.
By the way this is called port unification, and it is has apparently long been supported in Glassfish using Grizzly.
Outline
The basic idea is to produce an implementation of org.eclipse.jetty.server.Connector
which can look ahead at the first byte of the client's request. Luckily both HTTP and HTTPS have the client start the communication. For HTTPS (and TLS/SSL generally) the first byte will be 0x16
(TLS), or >= 0x80
(SSLv2). For HTTP the first byte will be good-old printable 7-bit ASCII. Now, depending on the first byte, the Connector
will either produce an SSL connection or a plain connection.
In the code here we take advantage of the fact that Jetty's SslSelectChannelConnector
itself extends SelectChannelConnector
, and has a newPlainConnection()
method (calling its superclass to produce a non-SSL connection) as well as a newConnection()
method (to produce an SSL connection). So our new Connector
can extend SslSelectChannelConnector
and delegate to one of those methods after observing the first byte from the client.
Unfortunately, we will be expected to create an instance of AsyncConnection
before the first byte is available. Some methods of that instance may even be called before the first byte is available. So we create a LazyConnection implements AsyncConnection
which can figure out later which kind of connection it will delegate to, or even return sensible default responses to some methods before it knows.
Being NIO-based, our Connector
will work with a SocketChannel
. Luckily we can extend SocketChannel
to create a ReadAheadSocketChannelWrapper
which delegates to the "real" SocketChannel
but can inspect and store the first bytes of the client's message.
Some Details
One very hacky bit. One of the methods our Connector
must override is customize(Endpoint,Request)
. If we end up with an SSL-based Endpoint
we can just pass to our superclass; otherwise the superclass will throw a ClassCastException
, but only after both passing to its superclass and setting the scheme on the Request
. So we pass to the superclass, but undo setting the scheme when we see the exception.
We also override isConfidential()
and isIntegral()
to ensure that our servlets can properly use HttpServletRequest.isSecure()
to figure out whether HTTP or HTTPS was used.
Attempting to read the first byte from the client may throw an IOException
, but we may have to attempt that in a place where an IOException
isn't expected, in which case we keep the exception around and throw it later.
Extending SocketChannel
looks different in Java >= 7 and Java 6. In the latter case, just comment out the methods that Java 6 SocketChannel
doesn't have.
The Code
public class PortUnificationSelectChannelConnector extends SslSelectChannelConnector {
public PortUnificationSelectChannelConnector() {
super();
}
public PortUnificationSelectChannelConnector(SslContextFactory sslContextFactory) {
super(sslContextFactory);
}
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException {
return super.newEndPoint(new ReadAheadSocketChannelWrapper(channel, 1), selectSet, key);
}
@Override
protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endPoint) {
return new LazyConnection((ReadAheadSocketChannelWrapper)channel, endPoint);
}
@Override
public void customize(EndPoint endpoint, Request request) throws IOException {
String scheme = request.getScheme();
try {
super.customize(endpoint, request);
} catch (ClassCastException e) {
request.setScheme(scheme);
}
}
@Override
public boolean isConfidential(Request request) {
if (request.getAttribute("javax.servlet.request.cipher_suite") != null) return true;
else return isForwarded() && request.getScheme().equalsIgnoreCase(HttpSchemes.HTTPS);
}
@Override
public boolean isIntegral(Request request) {
return isConfidential(request);
}
class LazyConnection implements AsyncConnection {
private final ReadAheadSocketChannelWrapper channel;
private final AsyncEndPoint endPoint;
private final long timestamp;
private AsyncConnection connection;
public LazyConnection(ReadAheadSocketChannelWrapper channel, AsyncEndPoint endPoint) {
this.channel = channel;
this.endPoint = endPoint;
this.timestamp = System.currentTimeMillis();
this.connection = determineNewConnection(channel, endPoint, false);
}
public Connection handle() throws IOException {
if (connection == null) {
connection = determineNewConnection(channel, endPoint, false);
channel.throwPendingException();
}
if (connection != null) return connection.handle();
else return this;
}
public long getTimeStamp() {
return timestamp;
}
public void onInputShutdown() throws IOException {
if (connection == null) connection = determineNewConnection(channel, endPoint, true);
connection.onInputShutdown();
}
public boolean isIdle() {
if (connection == null) connection = determineNewConnection(channel, endPoint, false);
if (connection != null) return connection.isIdle();
else return false;
}
public boolean isSuspended() {
if (connection == null) connection = determineNewConnection(channel, endPoint, false);
if (connection != null) return connection.isSuspended();
else return false;
}
public void onClose() {
if (connection == null) connection = determineNewConnection(channel, endPoint, true);
connection.onClose();
}
public void onIdleExpired(long l) {
if (connection == null) connection = determineNewConnection(channel, endPoint, true);
connection.onIdleExpired(l);
}
AsyncConnection determineNewConnection(ReadAheadSocketChannelWrapper channel, AsyncEndPoint endPoint, boolean force) {
byte[] bytes = channel.getBytes();
if ((bytes == null || bytes.length == 0) && !force) return null;
if (looksLikeSsl(bytes)) {
return PortUnificationSelectChannelConnector.super.newConnection(channel, endPoint);
} else {
return PortUnificationSelectChannelConnector.super.newPlainConnection(channel, endPoint);
}
}
// TLS first byte is 0x16
// SSLv2 first byte is >= 0x80
// HTTP is guaranteed many bytes of ASCII
private boolean looksLikeSsl(byte[] bytes) {
if (bytes == null || bytes.length == 0) return false; // force HTTP
byte b = bytes[0];
return b >= 0x7F || (b < 0x20 && b != '\n' && b != '\r' && b != '\t');
}
}
static class ReadAheadSocketChannelWrapper extends SocketChannel {
private final SocketChannel channel;
private final ByteBuffer start;
private byte[] bytes;
private IOException pendingException;
private int leftToRead;
public ReadAheadSocketChannelWrapper(SocketChannel channel, int readAheadLength) throws IOException {
super(channel.provider());
this.channel = channel;
start = ByteBuffer.allocate(readAheadLength);
leftToRead = readAheadLength;
readAhead();
}
public synchronized void readAhead() throws IOException {
if (leftToRead > 0) {
int n = channel.read(start);
if (n == -1) {
leftToRead = -1;
} else {
leftToRead -= n;
}
if (leftToRead <= 0) {
start.flip();
bytes = new byte[start.remaining()];
start.get(bytes);
start.rewind();
}
}
}
public byte[] getBytes() {
if (pendingException == null) {
try {
readAhead();
} catch (IOException e) {
pendingException = e;
}
}
return bytes;
}
public void throwPendingException() throws IOException {
if (pendingException != null) {
IOException e = pendingException;
pendingException = null;
throw e;
}
}
private int readFromStart(ByteBuffer dst) throws IOException {
int sr = start.remaining();
int dr = dst.remaining();
if (dr == 0) return 0;
int n = Math.min(dr, sr);
dst.put(bytes, start.position(), n);
start.position(start.position() + n);
return n;
}
public synchronized int read(ByteBuffer dst) throws IOException {
throwPendingException();
readAhead();
if (leftToRead > 0) return 0;
int sr = start.remaining();
if (sr > 0) {
int n = readFromStart(dst);
if (n < sr) return n;
}
return sr + channel.read(dst);
}
public synchronized long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
throwPendingException();
if (offset + length > dsts.length || length < 0 || offset < 0) {
throw new IndexOutOfBoundsException();
}
readAhead();
if (leftToRead > 0) return 0;
int sr = start.remaining();
int newOffset = offset;
if (sr > 0) {
int accum = 0;
for (; newOffset < offset + length; newOffset++) {
accum += readFromStart(dsts[newOffset]);
if (accum == sr) break;
}
if (accum < sr) return accum;
}
return sr + channel.read(dsts, newOffset, length - newOffset + offset);
}
public int hashCode() {
return channel.hashCode();
}
public boolean equals(Object obj) {
return channel.equals(obj);
}
public String toString() {
return channel.toString();
}
public Socket socket() {
return channel.socket();
}
public boolean isConnected() {
return channel.isConnected();
}
public boolean isConnectionPending() {
return channel.isConnectionPending();
}
public boolean connect(SocketAddress remote) throws IOException {
return channel.connect(remote);
}
public boolean finishConnect() throws IOException {
return channel.finishConnect();
}
public int write(ByteBuffer src) throws IOException {
return channel.write(src);
}
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
return channel.write(srcs, offset, length);
}
@Override
protected void implCloseSelectableChannel() throws IOException {
channel.close();
}
@Override
protected void implConfigureBlocking(boolean block) throws IOException {
channel.configureBlocking(block);
}
// public SocketAddress getLocalAddress() throws IOException {
// return channel.getLocalAddress();
// }
//
// public <T> T getOption(java.net.SocketOption<T> name) throws IOException {
// return channel.getOption(name);
// }
//
// public Set<java.net.SocketOption<?>> supportedOptions() {
// return channel.supportedOptions();
// }
//
// public SocketChannel bind(SocketAddress local) throws IOException {
// return channel.bind(local);
// }
//
// public SocketAddress getRemoteAddress() throws IOException {
// return channel.getRemoteAddress();
// }
//
// public <T> SocketChannel setOption(java.net.SocketOption<T> name, T value) throws IOException {
// return channel.setOption(name, value);
// }
//
// public SocketChannel shutdownInput() throws IOException {
// return channel.shutdownInput();
// }
//
// public SocketChannel shutdownOutput() throws IOException {
// return channel.shutdownOutput();
// }
}
}
Solution 3:
Based on the Answer "Yes We Can" i build the code that works with current jetty 9.3.11 and i think some would be interested.
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
public class MyReadAheadEndpoint implements EndPoint {
/** real endpoint we are wrapping */ private final EndPoint endPoint;
/** buffer used to read start bytes */ private final ByteBuffer start ;
/** how many N start bytes to read */ private int leftToRead;
/** first N bytes */ private final byte[] bytes ;
/** buffered exception to throw next */ private IOException pendingException = null;
@Override public InetSocketAddress getLocalAddress () { return endPoint.getLocalAddress(); }
@Override public InetSocketAddress getRemoteAddress () { return endPoint.getRemoteAddress(); }
@Override public boolean isOpen () { return endPoint.isOpen(); }
@Override public long getCreatedTimeStamp () { return endPoint.getCreatedTimeStamp(); }
@Override public boolean isOutputShutdown () { return endPoint.isOutputShutdown(); }
@Override public boolean isInputShutdown () { return endPoint.isInputShutdown(); }
@Override public void shutdownOutput () { endPoint.shutdownOutput(); }
@Override public void close () { endPoint.close(); }
@Override public Object getTransport () { return endPoint.getTransport(); }
@Override public long getIdleTimeout () { return endPoint.getIdleTimeout(); }
@Override public Connection getConnection () { return endPoint.getConnection(); }
@Override public void onOpen () { endPoint.onOpen(); }
@Override public void onClose () { endPoint.onClose(); }
@Override public boolean isOptimizedForDirectBuffers() { return endPoint.isOptimizedForDirectBuffers(); }
@Override public boolean isFillInterested () { return endPoint.isFillInterested(); }
@Override public boolean flush (final ByteBuffer... v) throws IOException { return endPoint.flush(v); }
@Override public void setIdleTimeout (final long v) { endPoint.setIdleTimeout(v); }
@Override public void write (final Callback v, final ByteBuffer... b) throws WritePendingException { endPoint.write(v, b); }
@Override public void setConnection (final Connection v) { endPoint.setConnection(v); }
@Override public void upgrade (final Connection v) { endPoint.upgrade(v); }
@Override public void fillInterested (final Callback v) throws ReadPendingException { endPoint.fillInterested(v); }
@Override public int hashCode() { return endPoint.hashCode(); }
@Override public boolean equals(final Object obj) { return endPoint.equals(obj); }
@Override public String toString() { return endPoint.toString(); }
public byte[] getBytes() { if (pendingException == null) { try { readAhead(); } catch (final IOException e) { pendingException = e; } } return bytes; }
private void throwPendingException() throws IOException { if (pendingException != null) { final IOException e = pendingException; pendingException = null; throw e; } }
public MyReadAheadEndpoint(final EndPoint channel, final int readAheadLength){
this.endPoint = channel;
start = ByteBuffer.wrap(bytes = new byte[readAheadLength]);
start.flip();
leftToRead = readAheadLength;
}
private synchronized void readAhead() throws IOException {
if (leftToRead > 0) {
final int n = endPoint.fill(start);
if (n == -1) { leftToRead = -1; }
else { leftToRead -= n; }
if (leftToRead <= 0) start.rewind();
}
}
private int readFromStart(final ByteBuffer dst) throws IOException {
final int n = Math.min(dst.remaining(), start.remaining());
if (n > 0) {
dst.put(bytes, start.position(), n);
start.position(start.position() + n);
dst.flip();
}
return n;
}
@Override public synchronized int fill(final ByteBuffer dst) throws IOException {
throwPendingException();
if (leftToRead > 0) readAhead();
if (leftToRead > 0) return 0;
final int sr = start.remaining();
if (sr > 0) {
dst.compact();
final int n = readFromStart(dst);
if (n < sr) return n;
}
return sr + endPoint.fill(dst);
}
}
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.annotation.Name;
public class MySslConnectionFactory extends AbstractConnectionFactory {
private final SslContextFactory _sslContextFactory;
private final String _nextProtocol;
public MySslConnectionFactory() { this(HttpVersion.HTTP_1_1.asString()); }
public MySslConnectionFactory(@Name("next") final String nextProtocol) { this((SslContextFactory)null, nextProtocol); }
public MySslConnectionFactory(@Name("sslContextFactory") final SslContextFactory factory, @Name("next") final String nextProtocol) {
super("SSL");
this._sslContextFactory = factory == null?new SslContextFactory():factory;
this._nextProtocol = nextProtocol;
this.addBean(this._sslContextFactory);
}
public SslContextFactory getSslContextFactory() { return this._sslContextFactory; }
@Override protected void doStart() throws Exception {
super.doStart();
final SSLEngine engine = this._sslContextFactory.newSSLEngine();
engine.setUseClientMode(false);
final SSLSession session = engine.getSession();
if(session.getPacketBufferSize() > this.getInputBufferSize()) this.setInputBufferSize(session.getPacketBufferSize());
}
@Override public Connection newConnection(final Connector connector, final EndPoint realEndPoint) {
final MyReadAheadEndpoint aheadEndpoint = new MyReadAheadEndpoint(realEndPoint, 1);
final byte[] bytes = aheadEndpoint.getBytes();
final boolean isSSL;
if (bytes == null || bytes.length == 0) {
System.out.println("NO-Data in newConnection : "+aheadEndpoint.getRemoteAddress());
isSSL = true;
} else {
final byte b = bytes[0]; // TLS first byte is 0x16 , SSLv2 first byte is >= 0x80 , HTTP is guaranteed many bytes of ASCII
isSSL = b >= 0x7F || (b < 0x20 && b != '\n' && b != '\r' && b != '\t');
if(!isSSL) System.out.println("newConnection["+isSSL+"] : "+aheadEndpoint.getRemoteAddress());
}
final EndPoint plainEndpoint;
final SslConnection sslConnection;
if (isSSL) {
final SSLEngine engine = this._sslContextFactory.newSSLEngine(aheadEndpoint.getRemoteAddress());
engine.setUseClientMode(false);
sslConnection = this.newSslConnection(connector, aheadEndpoint, engine);
sslConnection.setRenegotiationAllowed(this._sslContextFactory.isRenegotiationAllowed());
this.configure(sslConnection, connector, aheadEndpoint);
plainEndpoint = sslConnection.getDecryptedEndPoint();
} else {
sslConnection = null;
plainEndpoint = aheadEndpoint;
}
final ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
final Connection connection = next.newConnection(connector, plainEndpoint);
plainEndpoint.setConnection(connection);
return sslConnection == null ? connection : sslConnection;
}
protected SslConnection newSslConnection(final Connector connector, final EndPoint endPoint, final SSLEngine engine) {
return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
}
@Override public String toString() {
return String.format("%s@%x{%s->%s}", new Object[]{this.getClass().getSimpleName(), Integer.valueOf(this.hashCode()), this.getProtocol(), this._nextProtocol});
}
}
Solution 4:
You could implement this by writing a custom Jetty ConnectionFactory. I would suggest starting by copying and modifying the code of SslConnectionFactory and SslConnection. You need to inspect the first few bytes of the connection (buffering as necessary) to look for an SSL Client Hello. With an SSLv2 Hello, you can identify that by two length bytes, followed by 0x01, followed by the version bytes. An SSLv3 Hello starts with 0x16 followed by the version bytes. The version byte sequences will be 0x03 0x00 for SSL 3.0, 0x02 0x00 for SSL 2.0, 0x03 0x01 for TLS 1.0, 0x03 0x02 for TLS 1.1, 0x03 0x03 for TLS 1.2. Valid HTTP traffic should never begin with these byte sequences. (This answer has more details.) If it is SSL, pass it through the SSLEngine; if not, pass it directly to the next protocol connector.
Solution 5:
As of jetty-9.4.15.v20190215 support for port unification is built into Jetty, via the class OptionalSslConnectionFactory.
Here's an example class which, when run, will start up a server that listens on a single port 8000 and will respond to either HTTP or HTTPS. (This is based on the Jetty example code for separate HTTP and HTTPS connectors here.)
import java.io.*;
import javax.servlet.http.*;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class Jetty9PortUnification {
public static void main(String[] args) throws Exception {
// Use example keystore and keys from Jetty distribution
String keystorePath = "jetty-distribution/demo-base/etc/keystore";
File keystoreFile = new File(keystorePath);
if (!keystoreFile.exists()) {
throw new FileNotFoundException(keystoreFile.getAbsolutePath());
}
Server server = new Server();
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.setSecurePort(8000);
SecureRequestCustomizer src = new SecureRequestCustomizer();
httpConfig.addCustomizer(src);
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
ServerConnector portUnified = new ServerConnector(server,
new OptionalSslConnectionFactory(sslConnectionFactory, HttpVersion.HTTP_1_1.asString()),
sslConnectionFactory,
httpConnectionFactory);
portUnified.setPort(8000);
server.addConnector(portUnified);
server.setHandler(new AbstractHandler() {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/plain");
response.getWriter().println("Hello");
baseRequest.setHandled(true);
}
});
server.start();
server.join();
}
}
To run it you'll need javax.servlet-api-3.1.0.jar
, jetty-server-9.4.15.v20190215.jar
, jetty-util-9.4.15.v20190215.jar
, jetty-http-9.4.15.v20190215.jar
, and jetty-io-9.4.15.v20190215.jar
.