407

Is there a way to create a very basic HTTP server (supporting only GET/POST) in Java using just the Java SE API, without writing code to manually parse HTTP requests and manually format HTTP responses? The Java SE API nicely encapsulates the HTTP client functionality in HttpURLConnection, but is there an analog for HTTP server functionality?

Just to be clear, the problem I have with a lot of ServerSocket examples I've seen online is that they do their own request parsing/response formatting and error handling, which is tedious, error-prone, and not likely to be comprehensive, and I'm trying to avoid it for those reasons.

Lii
  • 11,553
  • 8
  • 64
  • 88
asker
  • 4,095
  • 3
  • 15
  • 3
  • 4
    Umm...the short answer is no. If you want something that handles post and get requests without manually writing the http headers then you could use servlets. But thats java ee. If you don't want to use something like that then sockets and manual parsing is the only other option I know of. – Matt Phillips Sep 17 '10 at 01:32
  • 5
    I know this isn't in the spirit of SO, but I would urge you to reconsider you distaste for Java EE API's. As some of the answers have mentioned, there are some very straight-forward implementations such as Jetty that allow you to embed a web server in your stand-alone application while still taking advantage of the servlet api. If you absolutely can't use the Java EE API for some reason than please disregard my comment :-) – Chris Thompson Sep 17 '10 at 03:05
  • 1
    "Servlets" are not really "Java EE". They are just a way of writing plugins that can be called by the surrounding application in response to message activity (these days, generally HTTP requests). Providing a servlet hosting environment "using just the Java SE API" is exactly what Jetty and Tomcat do. Of course you may want to *throw out unwanted complexity* but then you may need to decide on a subset of the allowed attributes and configurations of the GET/POST. It's often not worth it though, except for special security/embedded problems. – David Tonhofer Nov 19 '13 at 17:48
  • 1
    It might be worth going through this list of http servers before making a decision. http://java-source.net/open-source/web-servers –  Mar 19 '14 at 10:52

22 Answers22

571

Since Java SE 6, there's a builtin HTTP server in Sun Oracle JRE. The Java 9 module name is jdk.httpserver. The com.sun.net.httpserver package summary outlines the involved classes and contains examples.

Here's a kickoff example copypasted from their docs. You can just copy'n'paste'n'run it on Java 6+.
(to all people trying to edit it nonetheless, because it's an ugly piece of code, please don't, this is a copy paste, not mine, moreover you should never edit quotations unless they have changed in the original source)

package com.stackoverflow.q3732109;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class Test {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/test", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response";
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

}

Noted should be that the response.length() part in their example is bad, it should have been response.getBytes().length. Even then, the getBytes() method must explicitly specify the charset which you then specify in the response header. Alas, albeit misguiding to starters, it's after all just a basic kickoff example.

Execute it and go to http://localhost:8000/test and you'll see the following response:

This is the response


As to using com.sun.* classes, do note that this is, in contrary to what some developers think, absolutely not forbidden by the well known FAQ Why Developers Should Not Write Programs That Call 'sun' Packages. That FAQ concerns the sun.* package (such as sun.misc.BASE64Encoder) for internal usage by the Oracle JRE (which would thus kill your application when you run it on a different JRE), not the com.sun.* package. Sun/Oracle also just develop software on top of the Java SE API themselves like as every other company such as Apache and so on. Moreover, this specific HttpServer must be present in every JDK so there is absolutely no means of "portability" issue like as would happen with sun.* package. Using com.sun.* classes is only discouraged (but not forbidden) when it concerns an implementation of a certain Java API, such as GlassFish (Java EE impl), Mojarra (JSF impl), Jersey (JAX-RS impl), etc.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    It's from com.sun, so it's *not* part of Java but part of the Sun JVM. You can't count on them being available. Maybe they're renamed to com.oracle next week? – Waldheinz Jan 10 '11 at 08:46
  • 22
    @Waldheinz: like as @Software you're confusing `sun.*` with `com.sun.*`. For instance, do you see any documentation of `sun.*` API? Look here: http://java.sun.com/products/jdk/faq/faq-sun-packages.html Does it tell anything about `com.sun.*`? The `com.sun.*` is just used for their own public software which is not part of Java API. They also develop software on top of Java API, like as every other company. – BalusC Jan 10 '11 at 10:37
  • 1
    @BalusC: I would not take the presence of documentation as an guarantee that these packages are part of the Java API. I *guess* that http://java.sun.com/products/jdk/faq/faq-sun-packages.html applies to "sun.*" and "com.sun.*" equally, see also question #1331762 here on SO. Did I miss something here? – Waldheinz Jan 10 '11 at 10:39
  • 2
    @Waldheinz: you're not the only who generalizes the both packages. Think about it for a sec :) – BalusC Jan 10 '11 at 10:40
  • 8
    i think this is a very nice http server to use in integration test cases. thanks for the hint! – Andreas Petersson Jul 27 '12 at 13:56
  • he asked for JAVA SE solution, for me JAVA SE means always SUN/Oracle JAVA, so +1 – JIV Sep 21 '12 at 10:54
  • Its a bit extreme, but previously I extracted this into its own module since its a very nice bit of code and then its available wherever I need it (for tests only mind) - someone else has already pointed out a download: http://stackoverflow.com/a/7806273/484261 –  Dec 19 '12 at 10:14
  • 16
    If you're using Eclipse and get an error like "Access restriction: The type HttpExchange is not accessible due to restriction on required library ...", http://stackoverflow.com/a/10642163 tells how to disable that access check. – Samuli Pahaoja Sep 10 '13 at 09:55
  • @BalusC Is this package portable across different JREs? If not why? I created HTTP/HTTPs server using this package but I was asked to switch to Jetty since Jetty can be distributed across. – Niranjan Subramanian Oct 08 '14 at 04:36
  • 15
    FWIW this is also present in OpenJDK. – Jason C Feb 06 '15 at 06:37
  • 8
    The classes referred to here are tagged `@jdk.Exported` in the OpenJDK source code which means that the API is considered public and will be available on Java 9 (some other `com.sun.*` packages will become unavailable due to Project Jigsaw). – Jules Aug 05 '15 at 13:02
  • @BalusC Should i create a new `HttpHandler` class for each content? for example, it may i have 200 pages, should i create 200 classes?! – Sajad Aug 06 '15 at 20:00
  • Hey @BalusC, is it possible the get the full url inside the handler method? for example if I do http://localhost:8000/test?param1=value1&param2=value2 ? – Upsilon42 Nov 03 '15 at 20:59
  • 1
    @Upsilon42 Yes you can, just use: [t.getRequestURI()](https://docs.oracle.com/javase/7/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/HttpExchange.html#getRequestURI()) @BalusC When [HttpServer#setExecutor](https://docs.oracle.com/javase/8/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/HttpServer.html#setExecutor-java.util.concurrent.Executor-) isn't called - then default is used also. So no need to call `server.setExecutor(null)` – Nikita Kurtin Apr 10 '16 at 10:17
  • 1
    @Nikita: it was a copypaste from its documentation (which is indeed poor, as pointed out below code snippet in answer). – BalusC Apr 10 '16 at 10:21
  • To get simple GET request parameters, see this answer on SO: http://stackoverflow.com/a/17472462/341320 – juanmirocks Jul 27 '16 at 10:13
  • I tried running this code and it worked fine the first time, but due to the lack of `server.stop(0)` the port was locked up and so I had to use a different port. Has anyone found a way to avoid this in case it does happen? – Alerra Aug 25 '18 at 03:11
  • 1
    @Alerra: just stop the Java program. – BalusC Aug 25 '18 at 07:37
  • @BalusC See https://stackoverflow.com/questions/928211/how-to-shutdown-com-sun-net-httpserver-httpserver At least when I run it on my computer it persists long after my Java program is ended. I made sure to include a call to `stop()` now but I think its not as simple as closing the program. – Alerra Aug 25 '18 at 14:35
  • @BalusC Does it handle multiple requests simultaneously It provides multithreaded feature ? – xrcwrn Nov 02 '18 at 07:18
  • 2
    I will steal your way to name packages after question IDs. So far the best way to organize my answers I've seen. – Alex Pakka Apr 12 '19 at 19:49
  • @BalusC [JEP 403](https://openjdk.java.net/jeps/403) says the following, which seems to contradict your statement that it is okay to use the `com.sum.*` packages in general: "*Most com.sun.\* packages in the JDK are for internal use*". But the document explicitly makes a few exceptions, including the HTTP Server API. – Lii Aug 04 '21 at 21:29
  • @Lii: *.. in the JDK ..*. – BalusC Aug 05 '21 at 08:59
  • @Lii All the JNDI drivers are in sub-packages of that package, and you are definitely meant to use those. – user207421 May 05 '22 at 06:35
  • Not sure what the restriction was, but since these were exposed via the modules since JDK-18, they could have chosen the `java.net.http` route for telling these and not unnecessarily gotten into the complication of `com.sun.*` discussion being pulled again. – Naman Jan 01 '23 at 18:44
  • 1
    @Naman which packages (exposed since JDK-18) are you referring to? – Holger Jan 03 '23 at 15:21
  • @Holger I was referring to the package `com.sun.net.httpserver` of `jdk.httpserver` module in the context of a [Simple Web Server](https://openjdk.org/jeps/408). – Naman Jan 04 '23 at 02:24
  • 1
    @Naman but, at this answer said, this package exists since Java 6 and has been exposed as official API since JDK 9. Nothing has changed in JDK 18 in this regard. Just some new classes were added to the already existing module/package. – Holger Jan 04 '23 at 08:12
52

Check out NanoHttpd

NanoHTTPD is a light-weight HTTP server designed for embedding in other applications, released under a Modified BSD licence.

It is being developed at Github and uses Apache Maven for builds & unit testing"

Gray
  • 115,027
  • 24
  • 293
  • 354
letronje
  • 9,002
  • 9
  • 45
  • 53
  • 10
    One caution: It's likely that NanoHTTPD does not have protection against tree-walking attacks - you should check this if it will be serving on a public address. By this I mean attacks where a request like `GET /../../blahblah http/1.1` is issued and the server walks above the website root and into system file land, serving files that can be used to compromise or remotely attack the system, like a password file. – Lawrence Dol Sep 17 '10 at 04:51
  • 9
    That seems to be fixed. The current version generates a 403 if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 ). – Lena Schimmel May 04 '12 at 09:23
  • 13
    I don't understand how this is an answer to this question. – kimathie Sep 20 '17 at 08:31
32

The com.sun.net.httpserver solution is not portable across JREs. Its better to use the official webservices API in javax.xml.ws to bootstrap a minimal HTTP server...

import java.io._
import javax.xml.ws._
import javax.xml.ws.http._
import javax.xml.transform._
import javax.xml.transform.stream._

@WebServiceProvider
@ServiceMode(value=Service.Mode.PAYLOAD) 
class P extends Provider[Source] {
  def invoke(source: Source) = new StreamSource( new StringReader("<p>Hello There!</p>"));
}

val address = "http://127.0.0.1:8080/"
Endpoint.create(HTTPBinding.HTTP_BINDING, new P()).publish(address)

println("Service running at "+address)
println("Type [CTRL]+[C] to quit!")

Thread.sleep(Long.MaxValue)

EDIT: this actually works! The above code looks like Groovy or something. Here is a translation to Java which I tested:

import java.io.*;
import javax.xml.ws.*;
import javax.xml.ws.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

@WebServiceProvider
@ServiceMode(value = Service.Mode.PAYLOAD)
public class Server implements Provider<Source> {

    public Source invoke(Source request) {
        return  new StreamSource(new StringReader("<p>Hello There!</p>"));
    }

    public static void main(String[] args) throws InterruptedException {

        String address = "http://127.0.0.1:8080/";
        Endpoint.create(HTTPBinding.HTTP_BINDING, new Server()).publish(address);

        System.out.println("Service running at " + address);
        System.out.println("Type [CTRL]+[C] to quit!");

        Thread.sleep(Long.MAX_VALUE);
    }
}
Adriaan Koster
  • 15,870
  • 5
  • 45
  • 60
gruenewa
  • 1,666
  • 11
  • 16
  • 2
    +1 for being portable. Too bad you can't set the response content type as it is `text/xml`. – icza Sep 05 '14 at 12:23
  • 1
    I think you could do class Server implements Provider { ... and then specify the Content-Type within the DataSource's getContentType() method. Furthermore you can also inject the WebServiceContext: @Resource WebServiceContext ctx; to set other headers and to read request parameters. Unfortunately setting the content-type via WebServiceContext does not work. – gruenewa Sep 12 '14 at 20:09
  • 8
    Could you explain why com.sun.net.HttpServer isn't portable across JREs please? – javabeangrinder Nov 19 '14 at 15:56
  • @javabeangrinder As far as I know com.sun.* is not part of the offical Java APIs. Whereas javax.xml.ws.* is part of the official Java API. – gruenewa Nov 21 '14 at 19:04
  • Sorry for being a pain but when it works in OpenJDK, which it does, it should work everywhere, right? – javabeangrinder Nov 24 '14 at 07:29
  • 4
    No, l do not think so. It will not work on IBMs Java implementation and maybe also others. And even if it works now, internal APIs are allowed to change. Why not just use the the official API? – gruenewa Nov 26 '14 at 05:58
  • 3
    Quoting a comment on another post: "The classes referred to here are tagged @jdk.Exported in the OpenJDK source code which means that the API is considered public and will be available on Java 9" – slim Mar 24 '17 at 11:49
  • 5
    This link: http://docs.oracle.com/javase/9/docs/api/java.xml.ws-summary.html says that the java.xml.ws module is deprecated since Java 9. – Erel Segal-Halevi Oct 12 '17 at 22:49
  • 1
    Did somebody find a way to set the content type using this? – navy1978 Jan 30 '18 at 15:16
  • 1
    As stated in other answer, @com.sun@ package *is* part of the JDK. – Matthieu Dec 28 '20 at 14:09
  • 1
    The most interesting is that `com.sun.net.httpserver` is much more portable than `javax.xml.ws` – ZhekaKozlov Mar 30 '21 at 05:16
29

I like this question because this is an area where there's continuous innovation and there's always a need to have a light server especially when talking about embedded servers in small(er) devices. I think answers fall into two broad groups.

  1. Thin-server: server-up static content with minimal processing, context or session processing.
  2. Small-server: ostensibly a has many httpD-like server qualities with as small a footprint as you can get away with.

While I might consider HTTP libraries like: Jetty, Apache Http Components, Netty and others to be more like a raw HTTP processing facilities. The labelling is very subjective, and depends on the kinds of thing you've been call-on to deliver for small-sites. I make this distinction in the spirit of the question, particularly the remark about...

  • "...without writing code to manually parse HTTP requests and manually format HTTP responses..."

These raw tools let you do that (as described in other answers). They don't really lend themselves to a ready-set-go style of making a light, embedded or mini-server. A mini-server is something that can give you similar functionality to a full-function web server (like say, Tomcat) without bells and whistles, low volume, good performance 99% of the time. A thin-server seems closer to the original phrasing just a bit more than raw perhaps with a limited subset functionality, enough to make you look good 90% of the time. My idea of raw would be makes me look good 75% - 89% of the time without extra design and coding. I think if/when you reach the level of WAR files, we've left the "small" for bonsi servers that looks like everything a big server does smaller.

Thin-server options

Mini-server options:

  • Spark Java ... Good things are possible with lots of helper constructs like Filters, Templates, etc.
  • MadVoc ... aims to be bonsai and could well be such ;-)

Among the other things to consider, I'd include authentication, validation, internationalisation, using something like FreeMaker or other template tool to render page output. Otherwise managing HTML editing and parameterisation is likely to make working with HTTP look like noughts-n-crosses. Naturally it all depends on how flexible you need to be. If it's a menu-driven FAX machine it can be very simple. The more interactions, the 'thicker' your framework needs to be. Good question, good luck!

will
  • 4,799
  • 8
  • 54
  • 90
24

Have a look at the "Jetty" web server Jetty. Superb piece of Open Source software that would seem to meet all your requirments.

If you insist on rolling your own then have a look at the "httpMessage" class.

agilob
  • 6,082
  • 3
  • 33
  • 49
James Anderson
  • 27,109
  • 7
  • 50
  • 78
  • I think jetty api depends on servlet. – irreputable Sep 17 '10 at 03:00
  • 4
    @Irreputable: No, Jetty is a highly modular web server, which has a servlet container as one of it's optional modules. – Lawrence Dol Sep 17 '10 at 04:43
  • "is ther an analog for server functionality" -- yes its the "servlet" API. The servlet container calls your class after it has parsed the headers, cookies etc. – James Anderson Sep 17 '10 at 08:06
  • Software Monkey: you can't use it without servlet api. as long as we are using the silly notions of java se/ee, jetty is not se. – irreputable Sep 17 '10 at 14:51
  • 2
    Just for the record -- Jetty comes with its own implementation of the Servlet API and works just fine with Java SE – James Anderson Sep 12 '13 at 00:21
  • 4
    Jetty is too big and has too much of a learning curve before actual production usage becomes a possibility. –  Mar 19 '14 at 10:54
21

Once upon a time I was looking for something similar - a lightweight yet fully functional HTTP server that I could easily embed and customize. I found two types of potential solutions:

  • Full servers that are not all that lightweight or simple (for an extreme definition of lightweight.)
  • Truly lightweight servers that aren't quite HTTP servers, but glorified ServerSocket examples that are not even remotely RFC-compliant and don't support commonly needed basic functionality.

So... I set out to write JLHTTP - The Java Lightweight HTTP Server.

You can embed it in any project as a single (if rather long) source file, or as a ~50K jar (~35K stripped) with no dependencies. It strives to be RFC-compliant and includes extensive documentation and many useful features while keeping bloat to a minimum.

Features include: virtual hosts, file serving from disk, mime type mappings via standard mime.types file, directory index generation, welcome files, support for all HTTP methods, conditional ETags and If-* header support, chunked transfer encoding, gzip/deflate compression, basic HTTPS (as provided by the JVM), partial content (download continuation), multipart/form-data handling for file uploads, multiple context handlers via API or annotations, parameter parsing (query string or x-www-form-urlencoded body), etc.

I hope others find it useful :-)

amichair
  • 4,073
  • 1
  • 21
  • 19
  • The main method is a good example of basic usage, and the [FAQ](http://www.freeutils.net/source/jlhttp/faq) goes into many of the details. If you have suggestions for improving the existing docs, feel free to contact me directly! – amichair Apr 11 '19 at 06:15
  • Finally a practical, working server capable of serving an ISO image as virtual media for booting server over iLO as a normal user. Python's http.server or derivatives failed to serve such a large file whole or in chunks; this one did the job without using sudo. Thanks for sharing! – user3076105 Jan 05 '21 at 22:03
12

Spark is the simplest, here is a quick start guide: http://sparkjava.com/

Ali Shakiba
  • 20,549
  • 18
  • 61
  • 88
Laercio Metzner
  • 1,351
  • 11
  • 22
10

All the above answers details about Single main threaded Request Handler.

setting:

 server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());

Allows multiple request serving via multiple threads using executor service.

So the end code will be something like below:

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class App {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/test", new MyHandler());
        //Thread control is given to executor service.
        server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());
        server.start();
    }
    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response";
            long threadId = Thread.currentThread().getId();
            System.out.println("I am thread " + threadId );
            response = response + "Thread Id = "+threadId;
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}
Balu mallisetty
  • 603
  • 9
  • 19
9

It's possible to create an httpserver that provides basic support for J2EE servlets with just the JDK and the servlet api in a just a few lines of code.

I've found this very useful for unit testing servlets, as it starts much faster than other lightweight containers (we use jetty for production).

Most very lightweight httpservers do not provide support for servlets, but we need them, so I thought I'd share.

The below example provides basic servlet support, or throws and UnsupportedOperationException for stuff not yet implemented. It uses the com.sun.net.httpserver.HttpServer for basic http support.

import java.io.*;
import java.lang.reflect.*;
import java.net.InetSocketAddress;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

@SuppressWarnings("deprecation")
public class VerySimpleServletHttpServer {
    HttpServer server;
    private String contextPath;
    private HttpHandler httpHandler;

    public VerySimpleServletHttpServer(String contextPath, HttpServlet servlet) {
        this.contextPath = contextPath;
        httpHandler = new HttpHandlerWithServletSupport(servlet);
    }

    public void start(int port) throws IOException {
        InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
        server = HttpServer.create(inetSocketAddress, 0);
        server.createContext(contextPath, httpHandler);
        server.setExecutor(null);
        server.start();
    }

    public void stop(int secondsDelay) {
        server.stop(secondsDelay);
    }

    public int getServerPort() {
        return server.getAddress().getPort();
    }

}

final class HttpHandlerWithServletSupport implements HttpHandler {

    private HttpServlet servlet;

    private final class RequestWrapper extends HttpServletRequestWrapper {
        private final HttpExchange ex;
        private final Map<String, String[]> postData;
        private final ServletInputStream is;
        private final Map<String, Object> attributes = new HashMap<>();

        private RequestWrapper(HttpServletRequest request, HttpExchange ex, Map<String, String[]> postData, ServletInputStream is) {
            super(request);
            this.ex = ex;
            this.postData = postData;
            this.is = is;
        }

        @Override
        public String getHeader(String name) {
            return ex.getRequestHeaders().getFirst(name);
        }

        @Override
        public Enumeration<String> getHeaders(String name) {
            return new Vector<String>(ex.getRequestHeaders().get(name)).elements();
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            return new Vector<String>(ex.getRequestHeaders().keySet()).elements();
        }

        @Override
        public Object getAttribute(String name) {
            return attributes.get(name);
        }

        @Override
        public void setAttribute(String name, Object o) {
            this.attributes.put(name, o);
        }

        @Override
        public Enumeration<String> getAttributeNames() {
            return new Vector<String>(attributes.keySet()).elements();
        }

        @Override
        public String getMethod() {
            return ex.getRequestMethod();
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return is;
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public String getPathInfo() {
            return ex.getRequestURI().getPath();
        }

        @Override
        public String getParameter(String name) {
            String[] arr = postData.get(name);
            return arr != null ? (arr.length > 1 ? Arrays.toString(arr) : arr[0]) : null;
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return postData;
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return new Vector<String>(postData.keySet()).elements();
        }
    }

    private final class ResponseWrapper extends HttpServletResponseWrapper {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        final ServletOutputStream servletOutputStream = new ServletOutputStream() {

            @Override
            public void write(int b) throws IOException {
                outputStream.write(b);
            }
        };

        private final HttpExchange ex;
        private final PrintWriter printWriter;
        private int status = HttpServletResponse.SC_OK;

        private ResponseWrapper(HttpServletResponse response, HttpExchange ex) {
            super(response);
            this.ex = ex;
            printWriter = new PrintWriter(servletOutputStream);
        }

        @Override
        public void setContentType(String type) {
            ex.getResponseHeaders().add("Content-Type", type);
        }

        @Override
        public void setHeader(String name, String value) {
            ex.getResponseHeaders().add(name, value);
        }

        @Override
        public javax.servlet.ServletOutputStream getOutputStream() throws IOException {
            return servletOutputStream;
        }

        @Override
        public void setContentLength(int len) {
            ex.getResponseHeaders().add("Content-Length", len + "");
        }

        @Override
        public void setStatus(int status) {
            this.status = status;
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            this.status = sc;
            if (msg != null) {
                printWriter.write(msg);
            }
        }

        @Override
        public void sendError(int sc) throws IOException {
            sendError(sc, null);
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            return printWriter;
        }

        public void complete() throws IOException {
            try {
                printWriter.flush();
                ex.sendResponseHeaders(status, outputStream.size());
                if (outputStream.size() > 0) {
                    ex.getResponseBody().write(outputStream.toByteArray());
                }
                ex.getResponseBody().flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                ex.close();
            }
        }
    }

    public HttpHandlerWithServletSupport(HttpServlet servlet) {
        this.servlet = servlet;
    }

    @SuppressWarnings("deprecation")
    @Override
    public void handle(final HttpExchange ex) throws IOException {
        byte[] inBytes = getBytes(ex.getRequestBody());
        ex.getRequestBody().close();
        final ByteArrayInputStream newInput = new ByteArrayInputStream(inBytes);
        final ServletInputStream is = new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return newInput.read();
            }
        };

        Map<String, String[]> parsePostData = new HashMap<>();

        try {
            parsePostData.putAll(HttpUtils.parseQueryString(ex.getRequestURI().getQuery()));

            // check if any postdata to parse
            parsePostData.putAll(HttpUtils.parsePostData(inBytes.length, is));
        } catch (IllegalArgumentException e) {
            // no postData - just reset inputstream
            newInput.reset();
        }
        final Map<String, String[]> postData = parsePostData;

        RequestWrapper req = new RequestWrapper(createUnimplementAdapter(HttpServletRequest.class), ex, postData, is);

        ResponseWrapper resp = new ResponseWrapper(createUnimplementAdapter(HttpServletResponse.class), ex);

        try {
            servlet.service(req, resp);
            resp.complete();
        } catch (ServletException e) {
            throw new IOException(e);
        }
    }

    private static byte[] getBytes(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        while (true) {
            int r = in.read(buffer);
            if (r == -1)
                break;
            out.write(buffer, 0, r);
        }
        return out.toByteArray();
    }

    @SuppressWarnings("unchecked")
    private static <T> T createUnimplementAdapter(Class<T> httpServletApi) {
        class UnimplementedHandler implements InvocationHandler {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                throw new UnsupportedOperationException("Not implemented: " + method + ", args=" + Arrays.toString(args));
            }
        }

        return (T) Proxy.newProxyInstance(UnimplementedHandler.class.getClassLoader(),
                new Class<?>[] { httpServletApi },
                new UnimplementedHandler());
    }
}
f.carlsen
  • 445
  • 4
  • 8
9

Starting in Java 18, you can create simple web servers with Java standard library:

class Main {
    public static void main(String[] args) {
        var port = 8000;
        var rootDirectory = Path.of("C:/Users/Mahozad/Desktop/");
        var outputLevel = OutputLevel.VERBOSE;
        var server = SimpleFileServer.createFileServer(
                new InetSocketAddress(port),
                rootDirectory,
                outputLevel
        );
        server.start();
    }
}

This will, by default, show a directory listing of the root directory you specified. You can place an index.html file (and other assets like CSS and JS files) in that directory to show them instead.

Sidenote

For Java standard library HTTP client, see the post Java 11 new HTTP Client API.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
7

You may also have a look at some NIO application framework such as:

  1. Netty: http://jboss.org/netty
  2. Apache Mina: http://mina.apache.org/ or its subproject AsyncWeb: http://mina.apache.org/asyncweb/
ThiamTeck
  • 385
  • 1
  • 9
6

This code is better than ours, you only need to add 2 libs: javax.servelet.jar and org.mortbay.jetty.jar.

Class Jetty:

package jetty;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.mortbay.http.SocketListener;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHttpContext;

public class Jetty {

    public static void main(String[] args) {
        try {
            Server server = new Server();
            SocketListener listener = new SocketListener();      

            System.out.println("Max Thread :" + listener.getMaxThreads() + " Min Thread :" + listener.getMinThreads());

            listener.setHost("localhost");
            listener.setPort(8070);
            listener.setMinThreads(5);
            listener.setMaxThreads(250);
            server.addListener(listener);            

            ServletHttpContext context = (ServletHttpContext) server.getContext("/");
            context.addServlet("/MO", "jetty.HelloWorldServlet");

            server.start();
            server.join();

        /*//We will create our server running at http://localhost:8070
        Server server = new Server();
        server.addListener(":8070");

        //We will deploy our servlet to the server at the path '/'
        //it will be available at http://localhost:8070
        ServletHttpContext context = (ServletHttpContext) server.getContext("/");
        context.addServlet("/MO", "jetty.HelloWorldServlet");

        server.start();
        */

        } catch (Exception ex) {
            Logger.getLogger(Jetty.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
} 

Servlet class:

package jetty;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorldServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
    {
        String appid = httpServletRequest.getParameter("appid");
        String conta = httpServletRequest.getParameter("conta");

        System.out.println("Appid : "+appid);
        System.out.println("Conta : "+conta);

        httpServletResponse.setContentType("text/plain");
        PrintWriter out = httpServletResponse.getWriter();
        out.println("Hello World!");
        out.close();
    }
}
Alex K
  • 22,315
  • 19
  • 108
  • 236
leandro
  • 61
  • 1
  • 1
  • 4
    The question asks for a purely Java SE solution. You'll find that jetty implements the Java EE API. – Sridhar Sep 17 '12 at 15:58
  • Jetty runs perfectly well using standard Java SE and therefore fits the requirements. It *implements* parts of the Java EE API, it does not *need* it. There is a difference. – David Tonhofer Nov 19 '13 at 17:24
  • 2
    This does not qualify. _"using just the Java SE API"_. `*.Servlet.jar` and `*.jetty.jar` are obviously not part of Java SE. – icza Sep 05 '14 at 12:54
  • do i need to set jetty up? or can I just unclude those two jars and run this file? – Paul Preibisch Mar 23 '15 at 17:41
5

I can strongly recommend looking into Simple, especially if you don't need Servlet capabilities but simply access to the request/reponse objects. If you need REST you can put Jersey on top of it, if you need to output HTML or similar there's Freemarker. I really love what you can do with this combination, and there is relatively little API to learn.

Gray
  • 115,027
  • 24
  • 293
  • 354
Waldheinz
  • 10,399
  • 3
  • 31
  • 61
  • +1. I like the ideas behind Simple. However, problems come in when attempting to use HTTPS because Mamba takes away the "embeddable" feature from Simple. –  Mar 19 '14 at 11:40
5

An example of a very basic HTTP server on TCP sockets level:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class NaiveHttpServer {

  public static void main(String[] args) throws IOException {
    String hostname = InetAddress.getLocalHost().getHostName();
    ServerSocket serverSocket = new ServerSocket(8089);
    while (true) {
      Socket clientSocket = serverSocket.accept();
      PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
      BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
      String s = in.readLine();
      System.out.println(s);
      while ("\r\n".equals(in.readLine())); 
      if ("GET /hostname HTTP/1.1".equals(s)) {
        out.println("HTTP/1.1 200 OK");
        out.println("Connection: close");
        out.println("Content-Type: text/plain");
        out.println("Content-Length:" + hostname.length());
        out.println();
        out.println(hostname);
      } else {
        out.println("HTTP/1.1 404 Not Found");
        out.println("Connection: close");
        out.println();    
      }
      out.flush();
    }
  }
}

The example serves the hostname of the computer.

Juraj
  • 3,490
  • 4
  • 18
  • 25
3

checkout Simple. its a pretty simple embeddable server with built in support for quite a variety of operations. I particularly love its threading model..

Amazing!

Zuko
  • 2,764
  • 30
  • 30
3

Check out takes. Look at https://github.com/yegor256/takes for quick info

George
  • 7,206
  • 8
  • 33
  • 42
3

Try this https://github.com/devashish234073/Java-Socket-Http-Server/blob/master/README.md

This API has creates an HTTP server using sockets.

  1. It gets a request from the browser as text
  2. Parses it to retrieve URL info, method, attributes, etc.
  3. Creates dynamic response using the URL mapping defined
  4. Sends the response to the browser.

For example the here's how the constructor in the Response.java class converts a raw response into an http response:

public Response(String resp){
    Date date = new Date();
    String start = "HTTP/1.1 200 OK\r\n";
    String header = "Date: "+date.toString()+"\r\n";
    header+= "Content-Type: text/html\r\n";
    header+= "Content-length: "+resp.length()+"\r\n";
    header+="\r\n";
    this.resp=start+header+resp;
}
Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
2

How about Apache Commons HttpCore project?

From the web site:... HttpCore Goals

  • Implementation of the most fundamental HTTP transport aspects
  • Balance between good performance and the clarity & expressiveness of API
  • Small (predictable) memory footprint
  • Self contained library (no external dependencies beyond JRE)
Iqbal
  • 590
  • 1
  • 4
  • 15
  • That is probably too low-level. One should at least aim for a solution that calls your code at the servlet API level, unless one wants to deal with all the concepts like chunking, encoding etc. onself. It can be fun though. – David Tonhofer Nov 19 '13 at 17:35
1

You can write a pretty simple embedded Jetty Java server.

Embedded Jetty means that the server (Jetty) shipped together with the application as opposed of deploying the application on external Jetty server.

So if in non-embedded approach your webapp built into WAR file which deployed to some external server (Tomcat / Jetty / etc), in embedded Jetty, you write the webapp and instantiate the jetty server in the same code base.

An example for embedded Jetty Java server you can git clone and use: https://github.com/stas-slu/embedded-jetty-java-server-example

Johnny
  • 14,397
  • 15
  • 77
  • 118
1

The old com.sun.net.httpserver is again a public and accepted API, since Java 11. You can get it as HttpServer class, available as part of jdk.httpserver module. See https://docs.oracle.com/en/java/javase/11/docs/api/jdk.httpserver/com/sun/net/httpserver/HttpServer.html

This class implements a simple HTTP server. A HttpServer is bound to an IP address and port number and listens for incoming TCP connections from clients on this address. The sub-class HttpsServer implements a server which handles HTTPS requests.

So, apart from its limitations, there is no reason to avoid its use anymore.

I use it to publish a control interface in server applications. Reading the User-agent header from a client request I even respond in text/plain to CLI tools like curl or in more elegant HTML way to any other browser.

Cool and easy.

coterobarros
  • 941
  • 1
  • 16
  • 25
  • 1
    This solution has been already proposed by other users and `com.sun.net.httpserver.HttpServer` is more since Java 1.1 than since Java 11. – albertgumi Aug 22 '21 at 08:36
  • Sorry. You are partially right. It is there since 1.6. https://docs.oracle.com/en/java/javase/11/docs/api/jdk.httpserver/com/sun/net/httpserver/package-summary.html – coterobarros Aug 22 '21 at 09:14
  • Thank you. I've edited my answer to be clear about the important thing: the `HttpServer` class is accepted again as a proper class by Oracle. – coterobarros Aug 22 '21 at 09:25
1

Here is my simple webserver, used in JMeter for testing webhooks (that's why it will close and end itself after request is received).

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer {

    private static int extractContentLength(StringBuilder sb) {
        int length = 0;
        String[] lines = sb.toString().split("\\n");
        for (int i = 0; i < lines.length; i++) {
            String s = lines[i];
            if (s.toLowerCase().startsWith("Content-Length:".toLowerCase()) && i <= lines.length - 2) {
                String slength = s.substring(s.indexOf(":") + 1, s.length()).trim();
                length = Integer.parseInt(slength);
                System.out.println("Length = " + length);
                return length;
            }
        }
        return 0;
    }

    public static void main(String[] args) throws IOException {
        
        
        int port = Integer.parseInt(args[0]);
        System.out.println("starting HTTP Server on port " + port);

        StringBuilder outputString = new StringBuilder(1000);

        ServerSocket serverSocket = new ServerSocket(port);
        serverSocket.setSoTimeout(3 * 60 * 1000); // 3 minutes timeout
        while (true) {

            outputString.setLength(0); // reset buff

            Socket clientSocket = serverSocket.accept(); // blocking
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

            try {

                boolean isBodyRead = false;
                int dataBuffer;
                while ((dataBuffer = clientSocket.getInputStream().read()) != -1) {

                    if (dataBuffer == 13) { // CR
                        if (clientSocket.getInputStream().read() == 10) { // LF
                            outputString.append("\n");
                        }
                    } else {
                        outputString.append((char) dataBuffer);
                    }
                    
                    // do we have Content length
                    int len = extractContentLength(outputString);
                    if (len > 0) {
                        int actualLength = len - 1; // we need to substract \r\n
                        for (int i = 0; i < actualLength; i++) {
                            int body = clientSocket.getInputStream().read();
                            outputString.append((char) body);
                        }
                        isBodyRead = true;
                        break;
                    }

                } // end of reading while

                if (isBodyRead) {
                    // response headers
                    out.println("HTTP/1.1 200 OK");
                    out.println("Connection: close");
                    out.println(); // must have empty line for HTTP
                    
                    out.flush(); 
                    out.close(); // close clients connection
                }

            } catch (IOException ioEx) {
                System.out.println(ioEx.getMessage());
            }

            System.out.println(outputString.toString());
            break; // stop server - break while true
            
        } // end of outer while true
        
        serverSocket.close();

    } // end of method

}

You can test it like this:

curl -X POST -H "Content-Type: application/json" -H "Connection: close" -d '{"name": "gustinmi", "email": "gustinmi at google dot com "}' -v http://localhost:8081/
Mitja Gustin
  • 1,723
  • 13
  • 17
-2

I had some fun, I toyed around and pieced together this. I hope it helps you. You are going to need Gradle installed or use Maven with a plugin.

build.gradle

plugins {
    id 'application'
}

group 'foo.bar'
version '1.0'

repositories {
    mavenCentral()
}

application{
    mainClass.set("foo.FooServer")
}

dependencies {}

FooServer The main entry point, your main class.

package foo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FooServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(7654);
        serverSocket.setPerformancePreferences(0, 1, 2);

        /* the higher the numbers, the better the concurrent performance, ha!
           we found that a 3:7 ratio to be optimal
           3 partitioned executors to 7 network executors */

        ExecutorService executors = Executors.newFixedThreadPool(3);
        executors.execute(new PartitionedExecutor(serverSocket));
    }


    public static class PartitionedExecutor implements Runnable {
        ServerSocket serverSocket;

        public PartitionedExecutor(ServerSocket serverSocket) {
            this.serverSocket = serverSocket;
        }

        @Override
        public void run() {
            ExecutorService executors = Executors.newFixedThreadPool(30);
            executors.execute(new NetworkRequestExecutor(serverSocket, executors));
        }
    }


    public static class NetworkRequestExecutor implements Runnable{

        String IGNORE_CHROME = "/favicon.ico";
        String BREAK = "\r\n";
        String DOUBLEBREAK = "\r\n\r\n";

        Integer REQUEST_METHOD = 0;
        Integer REQUEST_PATH = 1;
        Integer REQUEST_VERSION = 2;

        String RENDERER;

        Socket socketClient;
        ExecutorService executors;
        ServerSocket serverSocket;

        public NetworkRequestExecutor(ServerSocket serverSocket, ExecutorService executors){
            this.serverSocket = serverSocket;
            this.executors = executors;
        }

        @Override
        public void run() {
            try {

                socketClient = serverSocket.accept();
                Thread.sleep(19);//do this for safari, its a hack but safari requires something like this.
                InputStream requestInputStream = socketClient.getInputStream();

                OutputStream clientOutput = socketClient.getOutputStream();

                if (requestInputStream.available() == 0) {
                    requestInputStream.close();
                    clientOutput.flush();
                    clientOutput.close();
                    executors.execute(new NetworkRequestExecutor(serverSocket, executors));
                    return;
                }

                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int bytesRead;
                while ((bytesRead = requestInputStream.read(byteBuffer.array())) != -1) {
                    byteArrayOutputStream.write(byteBuffer.array(), 0, bytesRead);
                    if (requestInputStream.available() == 0) break;
                }

                String completeRequestContent = byteArrayOutputStream.toString();
                String[] requestBlocks = completeRequestContent.split(DOUBLEBREAK, 2);

                String headerComponent = requestBlocks[0];
                String[] methodPathComponentsLookup = headerComponent.split(BREAK);
                String methodPathComponent = methodPathComponentsLookup[0];

                String[] methodPathVersionComponents = methodPathComponent.split("\\s");

                String requestVerb = methodPathVersionComponents[REQUEST_METHOD];
                String requestPath = methodPathVersionComponents[REQUEST_PATH];
                String requestVersion = methodPathVersionComponents[REQUEST_VERSION];


                if (requestPath.equals(IGNORE_CHROME)) {
                    requestInputStream.close();
                    clientOutput.flush();
                    clientOutput.close();
                    executors.execute(new NetworkRequestExecutor(serverSocket, executors));
                    return;
                }

                ConcurrentMap<String, String> headers = new ConcurrentHashMap<>();
                String[] headerComponents = headerComponent.split(BREAK);
                for (String headerLine : headerComponents) {
                    String[] headerLineComponents = headerLine.split(":");
                    if (headerLineComponents.length == 2) {
                        String fieldKey = headerLineComponents[0].trim();
                        String content = headerLineComponents[1].trim();
                        headers.put(fieldKey.toLowerCase(), content);
                    }
                }

                clientOutput.write("HTTP/1.1 200 OK".getBytes());
                clientOutput.write(BREAK.getBytes());

                Integer bytesLength = "hi".length();
                String contentLengthBytes = "Content-Length:" + bytesLength;
                clientOutput.write(contentLengthBytes.getBytes());
                clientOutput.write(BREAK.getBytes());

                clientOutput.write("Server: foo server".getBytes());
                clientOutput.write(BREAK.getBytes());

                clientOutput.write("Content-Type: text/html".getBytes());

                clientOutput.write(DOUBLEBREAK.getBytes());
                clientOutput.write("hi".getBytes());

                clientOutput.close();
                socketClient.close();

                executors.execute(new NetworkRequestExecutor(serverSocket, executors));

            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (InterruptedException ioException) {
                ioException.printStackTrace();
            }
        }
    }
}

Run it:

gradle run

Browse to:

http://localhost:7654/
Mike Croteau
  • 1,062
  • 2
  • 16
  • 43