reactor.io.net.http.HttpServer Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2011-2015 Pivotal Software Inc, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package reactor.io.net.http;
import org.reactivestreams.Publisher;
import reactor.Environment;
import reactor.bus.registry.Registration;
import reactor.bus.registry.Registries;
import reactor.bus.registry.Registry;
import reactor.bus.selector.Selector;
import reactor.core.Dispatcher;
import reactor.io.buffer.Buffer;
import reactor.io.codec.Codec;
import reactor.io.net.NetSelectors;
import reactor.io.net.ReactorChannelHandler;
import reactor.io.net.ReactorPeer;
import reactor.io.net.http.model.HttpHeaders;
import reactor.rx.Promise;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;
/**
* Base functionality needed by all servers that communicate with clients over HTTP.
*
* @param The type that will be received by this server
* @param The type that will be sent by this server
*
* @author Stephane Maldini
*/
public abstract class HttpServer
extends ReactorPeer> {
protected final Registry>> routedWriters;
private boolean hasWebsocketEndpoints = false;
protected HttpServer(Environment env, Dispatcher dispatcher, Codec codec) {
super(env, dispatcher, codec);
this.routedWriters = Registries.create();
}
/**
* Start the server without any global handler, only the specific routed methods (get, post...) will apply.
*
* @return a Promise fulfilled when server is started
*/
public Promise start() {
return start(null);
}
/**
* Get the address to which this server is bound. If port 0 was used on configuration, try resolving the port.
*
* @return the bind address
*/
public abstract InetSocketAddress getListenAddress();
/**
* Register an handler for the given Selector condition, incoming connections will query the internal registry
* to invoke the matching handlers. Implementation may choose to reply 404 if no route matches.
*
* @param condition a {@link Selector} to match the incoming connection with registered handler
* @param serviceFunction an handler to invoke for the given condition
*
* @return {@code this}
*/
@SuppressWarnings("unchecked")
public HttpServer route(
final Selector condition,
final ReactorChannelHandler> serviceFunction) {
routedWriters.register(condition, serviceFunction);
return this;
}
/**
* Listen for HTTP GET on the passed path to be used as a routing condition. Incoming connections will query the internal registry
* to invoke the matching handlers.
*
* e.g. "/test/{param}". Params are resolved using {@link HttpChannel#param(String)}
*
* @param path The {@link HttpSelector} to resolve against this path, pattern matching and capture are supported
* @param handler an handler to invoke for the given condition
*
* @return {@code this}
*/
public final HttpServer get(String path,
final ReactorChannelHandler> handler) {
route(NetSelectors.get(path), handler);
return this;
}
/**
* Listen for HTTP POST on the passed path to be used as a routing condition. Incoming connections will query the internal registry
* to invoke the matching handlers.
*
* e.g. "/test/{param}". Params are resolved using {@link HttpChannel#param(String)}
*
* @param path The {@link HttpSelector} to resolve against this path, pattern matching and capture are supported
* @param handler an handler to invoke for the given condition
* @return {@code this}
*/
public final HttpServer post(String path,
final ReactorChannelHandler> handler) {
route(NetSelectors.post(path), handler);
return this;
}
/**
* Listen for HTTP PUT on the passed path to be used as a routing condition. Incoming connections will query the internal registry
* to invoke the matching handlers.
*
* e.g. "/test/{param}". Params are resolved using {@link HttpChannel#param(String)}
*
* @param path The {@link HttpSelector} to resolve against this path, pattern matching and capture are supported
* @param handler an handler to invoke for the given condition
*
* @return {@code this}
*/
public final HttpServer put(String path,
final ReactorChannelHandler> handler) {
route(NetSelectors.put(path), handler);
return this;
}
/**
* Listen for WebSocket on the passed path to be used as a routing condition. Incoming connections will query the internal registry
* to invoke the matching handlers.
*
* e.g. "/test/{param}". Params are resolved using {@link HttpChannel#param(String)}
*
* @param path The {@link HttpSelector} to resolve against this path, pattern matching and capture are supported
* @param handler an handler to invoke for the given condition
*
* @return {@code this}
*/
public final HttpServer ws(String path,
final ReactorChannelHandler> handler) {
route(NetSelectors.get(path), handler);
hasWebsocketEndpoints = true;
return this;
}
/**
* Listen for HTTP DELETE on the passed path to be used as a routing condition. Incoming connections will query the internal registry
* to invoke the matching handlers.
*
* e.g. "/test/{param}". Params are resolved using {@link HttpChannel#param(String)}
*
* @param path The {@link HttpSelector} to resolve against this path, pattern matching and capture are supported
* @param handler an handler to invoke for the given condition
*
* @return {@code this}
*/
public final HttpServer delete(String path,
final ReactorChannelHandler> handler) {
route(NetSelectors.delete(path), handler);
return this;
}
protected abstract void onWebsocket(HttpChannel next);
protected final boolean hasWebsocketEndpoints(){
return hasWebsocketEndpoints;
}
protected Iterable extends Publisher> routeChannel(final HttpChannel ch) {
final List>>>
selected = routedWriters.select(ch);
if(hasWebsocketEndpoints) {
String connection = ch.headers().get(HttpHeaders.CONNECTION);
if (connection != null && connection.equals(HttpHeaders.UPGRADE)) {
onWebsocket(ch);
}
}
return new Iterable>() {
@Override
public Iterator> iterator() {
final Iterator>>>
iterator = selected.iterator();
return new Iterator>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public void remove() {
iterator.remove();
}
//Lazy apply
@Override
@SuppressWarnings("unchecked")
public Publisher next() {
Registration>> next = iterator.next();
if (next != null) {
ch.paramsResolver(next.getSelector().getHeaderResolver());
return next.getObject().apply(ch);
} else {
return null;
}
}
};
}
};
}
}