Ready Java server. The "lightest" Java server
Is there a way to create a very simple HTTP server (supporting only GET/POST) in Java using only the Java SE API, without writing code to manually parse HTTP requests and manually format HTTP responses? The Java SE API nicely encapsulates HTTP client functionality in HttpURLConnection, but is there similar functionality for an HTTP server?
Just to be clear, the problem I have with a lot of the ServerSocket examples I've seen online is that they do their own request parsing/response algorithm and error handling, which is tedious, error-prone, and unlikely be all-encompassing, and I try to avoid it for these reasons.
As an example of manual HTTP manipulation that I'm trying to avoid:
18 answers
Starting with Java SE 6, the Sun Oracle JRE has a built-in HTTP server. The com.sun.net.httpserver package summary describes the participating classes and provides examples.
Here's a starting example, copied from their docs (however, anyone trying to edit it because it's a terrible piece of code, please don't copy, not mine, moreover, you should never edit quotes unless they've changed in the original source). You can just copy and run it on Java 6+.
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(); ) ) )
It should be noted that the response.length() part of their example is bad, it should have been response.getBytes().length . Even in this case, the getBytes() method must explicitly specify the encoding, which you then specify in the response header. Alas, although misleading for beginners, it is, after all, just a basic example.
Execute it and go to http://localhost:8000/test and you will see the following response:
Regarding the use of com.sun.* , note that this, contrary to what some developers think, is absolutely not prohibited by the common FAQs. Why developers shouldn't write programs called "solar" packages. This sun.misc.BASE64Encoder FAQ is about the sun.* package (Such as sun.misc.BASE64Encoder) for internal using Oracle JRE (which will thus kill your application if you run it on another JRE), not the com.sun.* package. Sun/Oracle also just develops software on top of the Java SE API like any other company like Apache and so on. The use of com.sun.* is recommended (but not prohibited) when it comes to implementation specific Java API such as GlassFish (Java EE impl), Mojarra (JSF impl), Jersey (JAX-RS impl) etc.,
The com.sun.net.httpserver solution is not portable via the JRE. It's better to use the official web services API in javax.xml.ws to load 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 ( def invoke(source: Source) = new StreamSource(new StringReader("
Hello There!
")); ) val address = "http://127.0.0.1:8080/" Endpoint.create(HTTPBinding.HTTP_BINDING, new P()).publish(address) println("Service running at "+address) println( "Type +[C] to quit!") Thread.sleep(Long.MaxValue)EDIT: this actually works! The above code looks like Groovy or something like that. Here's the Java translation 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
This jsp page receives a user object from the outside and uses EL syntax to display the values of its properties. It is worth noting that here the variables name and age are referenced, although they are private.
In the Java Resources/src folder, the HelloServlet.java file defines the HelloServlet servlet:
Import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/hello") public class HelloServlet extends HttpServlet ( protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException ( User tom = new User("Tom", 25); request.setAttribute("user", tom); getServletContext() .getRequestDispatcher("/user.jsp") .forward(request, response); ) )
The servlet creates a User object. To pass it to the user.jsp page, the "user" attribute is set via the call request.setAttribute("user", tom) . Next, there is a redirect to the user.jsp page. And thus the page will receive data from the servlet.
When I started working in Java EE, the server infrastructure seemed like a mysterious, capricious monster to me. And this despite the fact that I came to programming from an admin background. Filled the ear in Resin, check if it rises? Did you unpack successfully? One day, the admins and I spent half the night trying to understand why, after a seemingly successful deployment, requests were still being answered. old copy applications. With such dances with a tambourine, switching to GlassFish seemed a good decision, and abandoning ejb seemed even better. Some of my colleagues rejoiced at the power of Spring, others were looking for something simpler... I wrote servlets under tomcat, and when I did something very small, I simply integrated jetty.
The simpler the better. It is clear that there are non-trivial requirements, there are complex tasks. But let’s face it, how many simple, unloaded services have you implemented using overly powerful and complex tools? “I’m used to this framework and don’t want to lose my skill,” I usually hear. “You will leave and whoever supports this monster code will curse you,” I think in response.
Maybe I didn’t convince you to write simple things. by simple means, but look at how you can make a server-side java application without a server at all. It's interesting after all.
How, without a server at all? Write request processing yourself?
Well, no, what are you talking about? I'm for simple solutions, but not for bicycles. Everything has already been written and, moreover, already installed. The java server is already in your jdk. Just use it)
Import com.sun.net.httpserver.HttpServer; import java.io.IOException ; import java.net.InetSocketAddress ; import java.util.concurrent.Executors; public class Main ( private static final int PORT = 9090 ; public static void main(String args) throws IOException ( int port = PORT; if (args.length > 0 ) ( try ( port = Integer .parseInt (args[ 0 ] ) ; ) catch (Exception e) ( e.printStackTrace () ; ) ) HttpServer server = HttpServer.create (new InetSocketAddress(port) , 0 ) ; server.createContext ("/publish" , new PublishHandler() ) ; server.createContext ("/subscribe" , new SubscribeHandler() ) ; server.setExecutor (Executors.newCachedThreadPool () ) ; server.start () ; System .out .println ( "Server is listening on port"+port); ) )
com.sun.net.HttpServer is all we need. Our application will listen for requests on port 9090 or whatever will be specified java -jar our.jar here. We attach our handlers to contexts before the server starts. Everything is extremely simple. In order to focus on business logic in the handlers, we will create an abstract ancestor for them, where we will solve all the little things like reading the request parameters and logging the request-response pair:
Import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import java.io.IOException ; import java.io.OutputStream ; import java.text.SimpleDateFormat ; import java.util.Date ; import java.util.HashMap ; import java.util.Map ; import java.util.UUID ; public abstract class HttpHandlerBased implements HttpHandler ( protected abstract String doGet(HttpExchange exchange, Map<
String
, String>params); @Override public void handle(HttpExchange exchange) throws IOException ( String requestMethod = exchange.getRequestMethod () ; if (requestMethod.equalsIgnoreCase ( "GET" ) ) ( String uriStr = exchange.getRequestURI () .getQuery () ; String id = UUID .randomUUID () .toString () .replace ("-" , "" ) ; log(" " + uriStr) ; Map<
String
, String>pars = new HashMap<
String
, String>() ; String pairs; if (uriStr != null && uriStr.contains ("=" ) ) ( if (uriStr.contains ("&") ) pairs = uriStr.split ("&" ) ; else pairs = new String ( uriStr) ; for ( String pair : pairs) ( String p = pair.split ("=" ) ; if (p.length == 2 ) ( pars.put (p[ 0 ] , p[ 1 ] ) ; ) ) ) String resp = doGet (exchange, pars) ; log(" " + resp) ; exchange.sendResponseHeaders(200, resp.length()); OutputStream body = exchange.getResponseBody(); body.write(resp.getBytes("UTF-8")); body.close(); ) ) public static void log(String msg) ( System .out .println (new SimpleDateFormat ("HH:mm:ss:SSS dd.MM.yy") .format (new Date () ) + " - " + msg) ; ) )
Having such an ancestor behind us, all we have to do is implement the doGet method in each specific “servlet”. I don’t process the POST method here because I don’t need it. But it’s no more difficult to process, you can try it yourself.
So what do we get?
For a "secret web server", essentially an internal API java, we have very nice tools for working with request data, response headers, etc. All this does not require any installation or configuration; it is right in the box and ready to go. Why not a tool for a simple web service? Of course, when we test our idea and think about production, we can transfer our business logic to “honest” servlets and use the same tomcat or resin. Why not? But if the project does not go into production, we will thank ourselves for not involving admins, not spending days deploying and setting up the environment, but simply going ahead and doing what was needed. And nothing more.
The best way to understand how something works is to do it yourself.
Having once become interested in network technologies and, among other things, servers, I came to the idea that it would be nice to write one such server myself using Java.
However, most of the Java literature that came across my eyes, if it explained something on the topic, did so only in the most general terms, without going further than an exchange text messages between client-server applications. On the Internet, such information was not found much more often, mainly in the form of grains of knowledge on educational sites or fragmentary information from users of various forums.
Thus, having collected this knowledge together and written a digestible server application capable of processing browser requests, I decided to distill this knowledge and describe step by step the process of creating a simple web server in Java.
I hope this article provides some useful knowledge for aspiring Java programmers and other people learning related technologies.
So, since the program assumes the simplest server functions, it will consist of one class without a graphical interface. This class (Server) inherits a stream and has one field - a socket:
Class Server extends Thread ( Socket s; )
In the main method, we create a new ServerSocket and set the port for it (in in this case port 1025 is used) and wait in an endless loop for a connection with the client. If there is a connection, we create a new thread by passing it the corresponding socket. If unsuccessful, we display an error message:
Try ( ServerSocket server = new ServerSocket(1025); while(true) ( new Server(server.accept()); ) ) catch(Exception e) ( System.out.println("Error: " + e); )
In order to make it possible to create a new thread in this way, we must naturally describe an appropriate constructor for it. In the constructor we mark the thread as a daemon and run it here:
Public Server(Socket socket) ( this.socket = socket; setDaemon(true); start(); )
Next, we describe the functionality of the stream in the run() method: first of all, we create a stream of outgoing and incoming data from the socket:
Public void run() ( try ( InputStream input = socket.getInputStream(); OutputStream output = socket.getOutputStream();
To read incoming data, we will use a buffer, which is a byte array of a certain size. Creating such a buffer is not necessary, because it is possible to receive incoming data in other ways, without limiting the amount of information received - but for the simplest web server described here, 64 kB is enough to receive the necessary data from the client.
In addition to the byte array, we also create an int variable that will store the number of bytes actually received by the buffer. This is necessary in order to subsequently create a client request string from the received data in a special way:
Byte buffer = new byte; int bytes = input.read(buffer); String request = new String(buf, 0, r);
The request line will contain the http request from the client. Among other information contained in this request, we are currently interested in the address of the requested file and its extension.
Without going into details of the structure of the http request, I will only say that the information we need will be in the first line of this request approximately in the following form:
GET /index.html HTTP/1.1
IN in this example a page is requested on the server at the address
/index.html
A page with this address is displayed on most servers by default. Our task is to extract this address from the request using our own written getPath() method. There are many options similar method and there is no point in citing them here. Key moment here is that having received the path to the desired file and writing it to the string variable path, we can try to create a file based on this data and, if successful, return this file, and if unsuccessful, return a specific error message:
String path = getPath(request); File file = new File(path);
We check whether this file is a directory. If such a file exists and is a directory, then we return the default file mentioned above - index.html:
Boolean exists = !file.exists(); if(!exists) if(file.isDirectory()) if(path.lastIndexOf(""+File.separator) == path.length()-1) ( path = path + "index.html"; ) else ( path = path + File.separator + "index.html"; file = new File(path); exists = !file.exists(); )
If the file is specified address does not exist, then we create an http response in the response line indicating that the file was not found, adding to in the right order the following headings:
If(exists)( String response = "HTTP/1.1 404 Not Found\n"; response +="Date: " + new Date() + "\n"; response +="Content-Type: text/plain\n "; response +="Connection: close\n"; response +="Server: Server\n"; response +="Pragma: no-cache\n\n"; response += "File " + path + " Not Found!";
After generating the response string, we send them to the client and close the connection:
Output.write(response.getBytes()); socket.close(); return; )
If the file exists, then before generating a response you need to find out its extension and, therefore, MIME type. First, we will find out the index of the point before the file extension and save it into an int variable.
Int ex = path.lastIndexOf(".");
Then we isolate the file extension that comes after it. The list of possible MIME types can be expanded, but in this case you will use only one form of 3 formats: html, jpeg and gif. By default we will use the MIME type for text:
String mimeType = “text/plain”; if(ex > 0) ( String format = path.substring(r); if(format.equalsIgnoreCase(".html")) mimeType = "text/html"; else if(format.equalsIgnoreCase(".jpeg")) mimeType = "image/jpeg"; else if(format.equalsIgnoreCase(".gif")) mimeType = "image/gif";
We formulate a response to the client:
String response = "HTTP/1.1 200 OK\n"; response += "Last-Modified: " + new Date(file.lastModified())) + "\n"; response += "Content-Length: " + file.length() + "\n"; response += "Content-Type: " + mimeType + "\n"; response +="Connection: close\n";
There must be two empty lines at the end of the headers, otherwise the response will not be processed correctly by the client.
Response += "Server: Server\n\n"; output.write(response.getBytes());
To send the file itself, you can use the following construction:
FileInputStream fis = new FileInputStream(path); int write = 1; while(write > 0) ( write = fis.read(buffer); if(write > 0) output.write(buffer, 0, write); ) fis.close(); socket.close(); )
Finally, we complete the try-catch block specified at the beginning.
Catch(Exception e) ( e.printStackTrace(); ) )
Since, as already mentioned, this web server implementation is one of the simplest, it can be easily modified by adding a graphical user interface, the number of supported extensions, a connection limiter, etc. In a word, the scope for creativity remains enormous. Go for it.
You can help and transfer some funds for the development of the site