Java: Simple HTTP Server application that responds in JSON
Asked Answered
S

5

10

I want to create a very simple HTTP server application in Java.

For example, if I run the server on localhost in port 8080, and I make to following call from my browser, I want to get a Json array with the string 'hello world!':

http://localhost:8080/func1?param1=123&param2=456

I would like to have in the server something that looks like this (very abstract code):

// Retunrs JSON String
String func1(String param1, String param2) {
    // Do Something with the params
    String jsonFormattedResponse = "['hello world!']";

    return jsonFormattedResponse;
}

I guess that this function should not actually "return" the json, but to send it using some HTTP response handler or something similar...

What it the simplest way to do it, without a need to get familiar with many kinds of 3rd party libraries that have special features and methodology?

Strickland answered 17/2, 2015 at 21:4 Comment(2)
Try looking up a tutorial on servlets. Example: tutorialspoint.com/servletsMyrick
Spring Boot makes this sort of thing very easy, but as you imply has a huge amount of complexity under the hood. It would be as easy as a couple of POM dependencies and about 5 lines of code though. There are good HOWTOs on the Spring Boot website.Shwalb
F
19

You could use classes from the package com.sun.net.httpserver:

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class JsonServer {
    private static final String HOSTNAME = "localhost";
    private static final int PORT = 8080;
    private static final int BACKLOG = 1;

    private static final String HEADER_ALLOW = "Allow";
    private static final String HEADER_CONTENT_TYPE = "Content-Type";

    private static final Charset CHARSET = StandardCharsets.UTF_8;

    private static final int STATUS_OK = 200;
    private static final int STATUS_METHOD_NOT_ALLOWED = 405;

    private static final int NO_RESPONSE_LENGTH = -1;

    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String ALLOWED_METHODS = METHOD_GET + "," + METHOD_OPTIONS;

    public static void main(final String... args) throws IOException {
        final HttpServer server = HttpServer.create(new InetSocketAddress(HOSTNAME, PORT), BACKLOG);
        server.createContext("/func1", he -> {
            try {
                final Headers headers = he.getResponseHeaders();
                final String requestMethod = he.getRequestMethod().toUpperCase();
                switch (requestMethod) {
                    case METHOD_GET:
                        final Map<String, List<String>> requestParameters = getRequestParameters(he.getRequestURI());
                        // do something with the request parameters
                        final String responseBody = "['hello world!']";
                        headers.set(HEADER_CONTENT_TYPE, String.format("application/json; charset=%s", CHARSET));
                        final byte[] rawResponseBody = responseBody.getBytes(CHARSET);
                        he.sendResponseHeaders(STATUS_OK, rawResponseBody.length);
                        he.getResponseBody().write(rawResponseBody);
                        break;
                    case METHOD_OPTIONS:
                        headers.set(HEADER_ALLOW, ALLOWED_METHODS);
                        he.sendResponseHeaders(STATUS_OK, NO_RESPONSE_LENGTH);
                        break;
                    default:
                        headers.set(HEADER_ALLOW, ALLOWED_METHODS);
                        he.sendResponseHeaders(STATUS_METHOD_NOT_ALLOWED, NO_RESPONSE_LENGTH);
                        break;
                }
            } finally {
                he.close();
            }
        });
        server.start();
    }

    private static Map<String, List<String>> getRequestParameters(final URI requestUri) {
        final Map<String, List<String>> requestParameters = new LinkedHashMap<>();
        final String requestQuery = requestUri.getRawQuery();
        if (requestQuery != null) {
            final String[] rawRequestParameters = requestQuery.split("[&;]", -1);
            for (final String rawRequestParameter : rawRequestParameters) {
                final String[] requestParameter = rawRequestParameter.split("=", 2);
                final String requestParameterName = decodeUrlComponent(requestParameter[0]);
                requestParameters.putIfAbsent(requestParameterName, new ArrayList<>());
                final String requestParameterValue = requestParameter.length > 1 ? decodeUrlComponent(requestParameter[1]) : null;
                requestParameters.get(requestParameterName).add(requestParameterValue);
            }
        }
        return requestParameters;
    }

    private static String decodeUrlComponent(final String urlComponent) {
        try {
            return URLDecoder.decode(urlComponent, CHARSET.name());
        } catch (final UnsupportedEncodingException ex) {
            throw new InternalError(ex);
        }
    }
}

On a side note, ['hello world!'] is invalid JSON. Strings must be enclosed in double quotes.

Felicidadfelicie answered 17/2, 2015 at 21:26 Comment(1)
Note: The httpserver package seems to be "restricted API" in Eclipse. To circumvent, see hereDarcie
J
1

If you are already familiar with servlet you do not need much to create a simple server to achieve what you want. But I would like to emphasize that your needs will likely to increase rapidly and therefore you may need to move to a RESTful framework (e.g.: Spring WS, Apache CXF) down the road.

You need to register URIs and get parameters using the standard servlet technology. Maybe you can start here: http://docs.oracle.com/cd/E13222_01/wls/docs92/webapp/configureservlet.html

Next, you need a JSON provider and serialize (aka marshall) it in JSON format. I recommend JACKSON. Take a look at this tutorial: http://www.sivalabs.in/2011/03/json-processing-using-jackson-java-json.html

Finally, your code will look similar to this:

public class Func1Servlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String p1 = req.getParameter("param1");
    String p2 = req.getParameter("param2");

    // Do Something with the params

    ResponseJSON resultJSON = new ResponseJSON();
    resultJSON.setProperty1(yourPropert1);
    resultJSON.setProperty2(yourPropert2);

    // Convert your JSON object into JSON string
    Writer strWriter = new StringWriter();
    mapper.writeValue(strWriter, resultJSON);
    String resultString = strWriter.toString();

    resp.setContentType("application/json");
    out.println(resultString );
  }
}

Map URLs in your web.xml:

<servlet>
  <servlet-name>func1Servlet</servlet-name>
  <servlet-class>myservlets.func1servlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>func1Servlet</servlet-name>
  <url-pattern>/func1/*</url-pattern>
</servlet-mapping>

Keep in mind this is a pseudo-code. There are lots you can do to enhance it, adding some utility classes, etc...

Nevertheless, as your project grows your need for a more comprehensive framework becomes more evident.

Janeljanela answered 17/2, 2015 at 21:40 Comment(2)
Thank you for your answer. How in your code can I define that it will be called only for func1? If I get a call with func2, I would like to respond differentlyStrickland
One way to do it, map func1 to a class and func2 to another class... there are many other ways to do it, but this is the simplest.Janeljanela
P
1

You could :

Install Apache Tomcat, and just drop a JSP into the ROOT project that implements this.

I second @xehpuk. It's not actually that hard to write your own single class HTTP server using just standard Java. If you want to do it in earlier versions you can use NanoHTTPD, which is a pretty well known single class HTTP server implementation.

I would personally recommend that you look into Apache Sling (pretty much THE Reference implementation of a Java REST api). You could probably implement your requirements here using Sling without ANY programming at all.

But as others have suggested, the standard way to do this is to create a java WAR and deploy it into a 'servlet container' such as Tomcat or Jetty etc.

Plasterboard answered 17/2, 2015 at 22:30 Comment(0)
O
0

Run main to start the server on port 8080

public class Main {
    public static void main(String[] args) throws LifecycleException {
        Tomcat tomcat = new Tomcat();
        Context context = tomcat.addContext("", null);

        Tomcat.addServlet(context, "func1", new HttpServlet() {
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
                Object response = func1(req.getParameter("param1"), req.getParameter("param2"));

                ObjectMapper mapper = new ObjectMapper();
                mapper.writeValue(resp.getWriter(), response);
            }
        });
        context.addServletMappingDecoded("/func1", "func1");

        tomcat.start();
        tomcat.getServer().await();
    }

    private static String[] func1(String p1, String p2) {
        return new String[] { "hello world", p1, p2 };
    }
}

Gradle dependencies:

dependencies {
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.28' // doesn't work with tomcat 9
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.4'
}
Onanism answered 8/3, 2018 at 18:28 Comment(0)
R
0

With the new SeBootStrap API, it gets simple:

// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

package jaxrs.examples.bootstrap;

import java.util.Collections;
import java.util.Set;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Application;

@ApplicationPath("helloworld")
@Path("hello")
public class HelloWorld extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        return Collections.singleton(HelloWorld.class);
    }

    @GET
    public String sayHello() {
        return "Hello, World!";
    }

}

Source: https://github.com/jakartaee/rest/blob/master/examples/src/main/java/jaxrs/examples/bootstrap/HelloWorld.java

This serves the String Hello, World! at helloworld/hello.

You need some boilerplate code:

public static void main(final String[] args) throws InterruptedException {
    SeBootstrap.start(HelloWorld.class).thenAccept(instance -> {
        instance.stopOnShutdown(stopResult ->
                System.out.printf("Stop result: %s [Native stop result: %s].%n", stopResult,
                        stopResult.unwrap(Object.class)));
        final URI uri = instance.configuration().baseUri();
        System.out.printf("Instance %s running at %s [Native handle: %s].%n", instance, uri,
                instance.unwrap(Object.class));
        System.out.println("Send SIGKILL to shutdown.");
    });

    Thread.currentThread().join();
}

For creating JSON, I recommond two APIs:

  • GSon as a very slim API
  • Jackson. You will find dozens of tutorials out there.

A full tutorial can be found at https://headcrashing.wordpress.com/2022/05/28/coding-microservice-from-scratch-part-1-of-7-jax-rs-done-right-head-crashing-informatics-53/.

Ragucci answered 3/4, 2023 at 21:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.