Reverse Proxy capabilities within your Spring-Boot application
Kein Apache httpd, kein nginx, nur deine Spring-Boot-Anwendung
Auf zum nächsten Artikel im Spring-Boot Umfeld. Heute schauen wir uns an, wie wir andere Dienste, die eigentlich unter einer anderen Adresse erreichbar sind, nativ über unsere Applikation erreichbar machen.
Als Beispiel soll die Integration einer Elasticsearch-Instanz dienen. Elasticsearch - um kurz einen Ausflug zu unternehmen
- ist eine äußerst skalierbare Volltext-Suche mit der großartigen Eigenschaft, dass sie über ein REST-Interface angesprochen
werden kann. Damit man mit Elasticsearch loslegen kann, muss man nichts anderes machen als das Binary auf
elastic.co herunterladen, entpacken und die entpackte Datei
bin/elasticsearch
starten. Geht man darafhin auf die Adresse http://localhost:9200
wird man von einem JSON begrüßt,
das einem ein paar Informationen zu dem gestarteten Elasticsearch-Cluster gibt.
Da Elasticsearch nur über Plugins abgesichert werden kann, sollte man besser keine plain Elasticsearch-Instanz auf irgendeinem Server starten ohne sich dessen sehr bewusst zu sein. Das erste was man in jedem Fall tun sollte ist - neben einer entsprechenden Firewall-Regel - ein Binding auf localhost. So… Genug von dem Ausflug. ;)
Wir wollen in jedem Fall dafür sorgen, dass wir die Elasticsearch-Instanz über unsere Webapplikation aufrufen können -
sie also zum Beispiel via http://localhost:8080/elasticsearch
erreichen.
Dies bietet uns bspw. die Möglichkeit die REST-Endpunkte der Elasticsearch mittels Spring-Security abzusichern. Wir könnten definieren,
welche User mit welchen Rollen Zugriff auf welche Ressourcen besitzen. Wie man das macht werde ich in einem anderen Artikel
erklären, der noch in Planung ist ;)
David Smiley’s Reverse-Proxy-Servlet
Es gibt da einen Menschen namens David Smiley und dieser hat vor recht langer Zeit bereits ein ziemlich cooles Stück Code geschrieben, nämlich sein Reverse-Proxy-Servlet welches wir benutzen werden um die gewünschte Funktionalität herzustellen.
Setup
Wieder einmal findet ihr ein entsprechendes Projekt auf meinem Github Account.
Im Kern ist gar nicht allzu viel zu tun. Wir haben in unserer pom.xml
folgende Dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mitre.dsmiley.httpproxy</groupId>
<artifactId>smiley-http-proxy-servlet</artifactId>
<version>1.7</version>
</dependency>
…sowie eine SpringBootApplication
:
@SpringBootApplication
public class ReverseProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ReverseProxyApplication.class, args);
}
}
Damit läuft zumindest schonmal unsere Applikation - natürlich ohne irgendeinen Zweck zu erfüllen. ;)
Remote-Servlet Konfiguration
Was wir jetzt brauchen ist ein Servlet, dass Requests unter einem bestimmten Pfad entgegennimmt und an
die Elasticsearch weiterreicht. Da wir ein wunderschönes Spring-Boot-Projekt haben, welches ohne web.xml
auskommt, müssen wir uns über den Spring-Way eine Komponente bauen, die uns eine ServletRegistrationBean
liefert.
@Configuration
public class ElasticsearchServletConfiguration {
@Bean(name = "elasticsarchServlet")
public ServletRegistrationBean elasticsearchServletRegistrationBean() {
ProxyServlet proxyServlet = new ProxyServlet();
Map<String, String> initParams = new HashMap<>();
initParams.put("targetUri", "http://localhost:9200");
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(proxyServlet);
servletRegistrationBean.setName("elasticsearch");
servletRegistrationBean.setUrlMappings(new ArrayList<>(Arrays.asList("elasticsearch/*")));
servletRegistrationBean.setInitParameters(initParams);
return servletRegistrationBean;
}
}
In Zeile 6 defininieren wir uns zunächst mal ein ProxyServlet
, welches wir in Zeile 11
an die ServletRegistrationBean
übergeben. Der Bean geben wir dann noch die init-Parameter für das
ProxyServlet
sowie ein URL-Mapping mit…. und fertig.
Da ich ein Fan von ausgelagerten Konfigurationen bin, packen wir noch ein paar Properties in unsere
application.properties
und passen unsere Servlet-Konfiguration entsprechend an.
proxy.elasticsearch.targetUri=http://localhost:9200
proxy.elasticsearch.servletUrl=/elasticsearch/*
@Configuration
public class ElasticsearchServletConfiguration {
@Value("${proxy.elasticsearch.targetUri}")
private String targetUri;
@Value("${proxy.elasticsearch.servletUrl}")
private String servletUrl;
@Bean(name = "elasticsarchServlet")
public ServletRegistrationBean elasticsearchServletRegistrationBean() {
ProxyServlet proxyServlet = new ProxyServlet();
Map<String, String> initParams = new HashMap<>();
initParams.put("targetUri", targetUri);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(proxyServlet);
servletRegistrationBean.setName("elasticsearch");
servletRegistrationBean.setUrlMappings(new ArrayList<>(Arrays.asList(servletUrl)));
servletRegistrationBean.setInitParameters(initParams);
return servletRegistrationBean;
}
}
Ein Aufruf von http://localhost:8080/elasticsearch
liefert uns jetzt ein wunderhübsches JSON mit unseren Cluster-Informationen. YAY!!
{
"name" : "Eric Slaughter",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "2.2.0",
"build_hash" : "8ff36d139e16f8720f2947ef62c8167a888992fe",
"build_timestamp" : "2016-01-27T13:32:39Z",
"build_snapshot" : false,
"lucene_version" : "5.4.1"
},
"tagline" : "You Know, for Search"
}
Fazit
Wir haben gesehen, dass es mit wenigen Zeilen Code möglich ist andere Dienste hinter einer URL seiner Spring-Boot-Applikation zu platzieren. Damit können wir jetzt beispielsweise unser Applikations-Berechtigungssystem oder unser Logging für die integrierten Endpunkte nutzen! :)