DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Easily Update and Reload SSL for a Server and an HTTP Client

Trending

  • Integration Isn’t a Task — It’s an Architectural Discipline
  • Integrating Model Context Protocol (MCP) With Microsoft Copilot Studio AI Agents
  • Metrics at a Glance for Production Clusters
  • How to Perform Custom Error Handling With ANTLR

HTTP and HTTP/S Proxies with Jetty

By 
Alan Hohn user avatar
Alan Hohn
·
Oct. 15, 13 · Interview
Likes (1)
Comment
Save
Tweet
Share
42.0K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

I’ve talked before about Jetty as an embedded servlet container. Jetty also includes some useful utility servlet implementations, one of which isProxyServlet.

ProxyServlet is a way to create an HTTP or HTTP/S proxy in very few lines of code. Even though it’s part of the Jetty project, it’s modularized to be independent of the Jetty server, so you can use it even in cases where the servlet won’t be run in Jetty.

Motivation

Why might you need a proxy servlet? One reason is to address issues raised by the same origin policy. In general, a script loaded from one site is not allowed to make requests from a different site. While it is possible to work around this (for example using JSONP) I tend to think a proxy is a more elegant solution as it doesn’t require exploiting a hole to download and evaluate arbitrary JavaScript.

A proxy might also be useful to allow a user to access a web service without providing all the information necessary to access it. In our example, we’ll be providing a proxy for Google’s Places API without having to send the Google API key down to the browser.

The proxy we’ll be looking at is a per-request proxy, so it’s not something that could conveniently be used for caching remote server responses in case of slow connections or server failures.

Example

The example is part of the Spring WebMVC application I use to present WebMVC and REST for a Java class. I’ve added the PlacesProxyServletand a basic HTML page to demonstrate fetching Google Places search results and using them in jQuery.

Maven POM

To get started, we need jetty-proxy in our pom.xml. Prior to Jetty 9, theProxyServlet class lived in jetty-servlets, but it’s been moved, probably to reduce the other Jetty dependencies that have to be pulled in.

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-proxy</artifactId>
    <version>${jetty.version}</version>
</dependency>

Java

Next, we create a class that extends ProxyServlet. We need to know the right URI to use for Google Places, and we need a Google API key. The best way to handle this is to allow them to be passed in from the servlet context using init-param, but I like to also allow them to be overridden using Java system properties. We start by overriding the init() method:

public void init() throws ServletException {
    super.init();
    ServletConfig config = getServletConfig();
    placesUrl = config.getInitParameter("PlacesUrl");
    apiKey = config.getInitParameter("GoogleApiKey");
    // Allow override with system property
    try {
        placesUrl = System.getProperty("PlacesUrl", placesUrl);
        apiKey = System.getProperty("GoogleApiKey", apiKey);
    } catch (SecurityException e) {
    }
    if (null == placesUrl) {
        placesUrl = "https://maps.googleapis.com/maps/api/place/search/json";
    }
}

To actually proxy the requests, the key method is rewriteURI. Again, this is new to Jetty 9; previously there was a method called proxyHttpURI that accomplished pretty much the same function.

protected URI rewriteURI(HttpServletRequest request) {
    String query = request.getQueryString();
    return URI.create(placesUrl + "?" + query + "&key=" + apiKey);
}

This method returns the “real” URI that the Jetty proxy servlet will call. All of the data from the client request is available. In this case, we just need the browser’s query parameters so we can pass them on to Google Places.

Tweaks

To actually get this to work with the Google Places API, there were a couple other changes required. First, the Places API enforces HTTP/S. Note that this doesn’t mean that our client has to connect to our proxy servlet using HTTP/S; regular HTTP is perfectly fine for that connection because our proxy servlet is making a brand new HTTP/S connection (using Jetty’s HttpClientclass). However, it does mean that we need to tell the Jetty HttpClient that it’s OK to use HTTP/S. We do this by overriding the method that theProxyServlet class uses to make a new HttpClient:

protected HttpClient newHttpClient() {
    SslContextFactory sslContextFactory = new SslContextFactory();
    HttpClient httpClient = new HttpClient(sslContextFactory);
    return httpClient;
}

Second, Google Places didn’t like the fact that the Jetty proxy servlet adds aHost header to the request with the name of the originating server. With this header, the Google Places server returns 404 in response to the request. Fortunately, this is easy to fix; we just have to remove that header before the request goes out. We can do this by overriding the customizeProxyRequestmethod that ProxyServlet thoughtfully provides for just such a problem:

protected void customizeProxyRequest(Request proxyRequest,
        HttpServletRequest request) {
    proxyRequest.getHeaders().remove("Host");
}

Updates to web.xml

To get this servlet up and running, we need to add it to web.xml. In the case of the example application, this required updating to Servlet 3.0, since the Jetty proxy servlet wants to use asynchronous connections. This is a good thing in terms of increasing the number of simulataneous requests the proxy servlet can process, but it requires enabling that feature in web.xml:

<servlet>
    <servlet-name>PlacesProxy</servlet-name>
    <servlet-class>org.anvard.webmvc.server.PlacesProxyServlet</servlet-class>
    <init-param>
      <param-name>GoogleApiKey</param-name>
      <param-value>YOUR_KEY_HERE</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

<servlet-mapping>
    <servlet-name>PlacesProxy</servlet-name>
    <url-pattern>/places</url-pattern>
</servlet-mapping>

The async-supported tag is important; the proxy servlet won’t work without it.

Browser interface

On the browser side, we need a way to query and then display the results. I cannibalized some example HTML and JavaScript I had lying around that did something similar with CometD. (Unfortunately, I can’t find the original source to provide a linkback.) The relevant jQuery part looks like this:

$.getJSON("/places?location=39.016249,-77.122993&radius=1000&types=food&sensor=false",
    function ( data ) {
        console.log( data );
        for (i = 0; i < data.results.length; i++) {
            result =data.results[i];
            $('<li>').html(result.name + '<br>' + result.vicinity).appendTo('#contentList');
        }
    })
    .fail(function() {
        console.log( "error" );
    })
    .always(function() {
        $("#status").text("Complete.");
    });

The jQuery makes an AJAX call to the proxy servlet, which then makes a call to Google Places. The resulting JSON response data is sent through as-is. The (anonymous) “success” function then gets called. It iterates through the returned results, adding <li> tags to the existing list for each result it finds.

Conclusion

Of course, a proxy servlet doesn’t have to be used for sites on the Internet. One of my motivations for creating the example application was to show how easy it was to REST-enable an existing standalone Java application. Many systems that use Java have multiple standalone Java applications, each performing some independent function. This would make it challenging to create a single unified web interface while still allowing each application to define its own REST API. Proxy servlets can help by making it look like there’s a single endpoint for all the various APIs, while not requiring any logic that knows about the contents of the interfaces.

Jetty (web server)

Published at DZone with permission of Alan Hohn, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Easily Update and Reload SSL for a Server and an HTTP Client

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!