I happened upon a GitHub gist containing a community created list of server one liners. The idea is that the server should fulfil the following:
After reviewing the list at the time, I noticed there were no Java entries that didn’t have some kind of dependency. I wrote a basic Java web server quite some years ago and decided it would be a good exercise in minimalism.
renatoathaydes commented on 10 Mar 2019:
Hi, your list is missing an entry for Java.
I am the author of a HTTP server that fits your requirements. You can install it as a single jar with:
0001 curl https://jcenter.bintray.com/com/athaydes/rawhttp/rawhttp-cli/1.0/rawhttp-cli-1.0-all.jar -o rawhttp.jar
Then, run with:
0002 java -jar ./rawhttp.jar serve . -p 8000
Works with Java 8+ including latest Java 11 versions.
Firstly, random binaries from some unknown place are disgusting - who knows what this code runs without decompiling it.
Secondly it feels as if the use of curl
is quite ridiculous, especially as it already has so much web capability within it’s own right.
informvisu commented on 5 Oct 2019:
All you need is JAVA 1.6+
./httpServer.sh
0003 /opt/java_1.6/bin/javac HTTPServer.java 0004 /opt/java_1.6/bin/java HTTPServer $1
HTTPServer.java
0005 import java.io.IOException; 0006 import java.io.OutputStream; 0007 import java.net.InetAddress; 0008 import java.net.InetSocketAddress; 0009 import java.util.Date; 0010 import com.sun.net.httpserver.HttpExchange; 0011 import com.sun.net.httpserver.HttpHandler; 0012 import com.sun.net.httpserver.HttpServer; 0013 0014 public class HTTPServer { 0015 static int port = 8000; 0016 public static void main(String[] args) throws Exception { 0017 if(args.length > 0) try { 0018 port = Integer.parseInt(args[0]); 0019 } catch (Exception e) {System.out.println("Invalid port number "+args[0]);} 0020 HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost(), port), 5); 0021 System.out.println("Server is listening at "+server.getAddress()); 0022 server.createContext("/", new MyHandler()); 0023 server.setExecutor(null); // creates a default executor 0024 server.start(); 0025 } 0026 static class MyHandler implements HttpHandler { 0027 @Override 0028 public void handle(HttpExchange t) throws IOException { 0029 String response = "This is the response"; //TODO construct your own response 0030 t.sendResponseHeaders(200, response.length()); 0031 OutputStream os = t.getResponseBody(); 0032 os.write(response.getBytes()); 0033 os.close(); 0034 } 0035 } 0036 }
Okay, we’re getting there, but I still have a few complaints:
import java.io.*
to reduce to imports down to one.The solution I posted is here:
0037 echo -e 'import java.net.*;import java.nio.file.*;public class M{public static void main(String[] args)throws Exception{byte[]b=new byte[16777215];ServerSocket s=new ServerSocket(3333);for(;;)try{Socket c=s.accept();c.getInputStream().read(b);c.getOutputStream().write(("HTTP/1.1 200 OK\\r\\n\\r\\n"+new String(Files.readAllBytes(Paths.get(new String(b).split(" ")[1].substring(1))))).getBytes());c.getOutputStream().flush();c.close();}catch(Exception e){}}}'>M.java;javac M.java;java M
Okay, it’s quite compact and if we un-pack the Java source, we get something like this (with added comments for readability):
0038 /* Imports used by the program */ 0039 import java.net.*; 0040 import java.nio.file.*; 0041 0042 public class M{ 0043 /** 0044 * main() 0045 * 0046 * The main entry method into the program. Note that we ignore all command 0047 * line arguments, but Java insists we pretend to need them. 0048 * 0049 * @param args The command line arguments. 0050 **/ 0051 public static void main(String[] args) throws Exception{ 0052 /* A buffer for reading input files into */ 0053 byte[] b = new byte[16777215]; 0054 /* Start the server on the given port */ 0055 ServerSocket s = new ServerSocket(3333); 0056 /* Infinte loop the server */ 0057 for(;;) 0058 /* Catch any thrown exception */ 0059 try{ 0060 /* Block until there is a new connection */ 0061 Socket c = s.accept(); 0062 /* Read the input from the client (we could infinitely hang here!) */ 0063 c.getInputStream().read(b); 0064 /* Form a response */ 0065 c.getOutputStream().write( 0066 ( 0067 /* Basic HTTP response header */ 0068 "HTTP/1.1 200 OK\\r\\n\\r\\n" + 0069 /* Form the file content */ 0070 new String( 0071 Files.readAllBytes( 0072 Paths.get( 0073 /* Assume the second "part" is the fiel to be loaded */ 0074 /* Note that we reuse our input buffer as an output buffer */ 0075 new String(b).split(" ")[1].substring(1) 0076 /* The substring removes the forward-slash */ 0077 ) 0078 ) 0079 ) 0080 /* Convert String to byte array to be written */ 0081 ).getBytes() 0082 ); 0083 /* Make sure all of the bytes are written before closing */ 0084 c.getOutputStream().flush(); 0085 /* Be kind and close the stream off */ 0086 c.close(); 0087 }catch(Exception e){ 0088 /* We don't care if this loop dies, just don't crash the server */ 0089 } 0090 } 0091 }
I think it almost fulfils the task, but there are some very glaring issues (i.e. never use this code in production):
And that’s just to name a few that instantly stick out at me. For a small test server (on a private network), or teaching students the basics of web servers, it will suffice.
It would actually make a very nice project for cyber security students to explore and report on the very large attack surface, possibly firstly without seeing the code and then after getting to see the code.