Last active
December 22, 2020 07:17
-
-
Save pranavgade20/04943bb9c91e0b7e6cf3a598ecc74212 to your computer and use it in GitHub Desktop.
A simple, one-file HTTP file server(HFS) writen in Java without any external dependencies. I use this to stream videos from computer to other devices on the network.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.*; | |
import java.net.*; | |
import java.nio.file.Files; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.HashMap; | |
public class FileServer { | |
public static String BASE_DIR = "/"; | |
public static final int PORT = 8080; | |
static ServerSocket server; | |
public static void main(String[] args) { | |
try { | |
Collections.list(NetworkInterface.getNetworkInterfaces()).stream() | |
.flatMap(a -> Collections.list(a.getInetAddresses()).stream()) | |
.filter(InetAddress::isSiteLocalAddress) | |
.filter(a -> !a.isLoopbackAddress()) | |
.forEach(a -> System.out.println(a.getHostAddress())); | |
server = new ServerSocket(PORT); | |
System.out.println("Listening on port: " + PORT); | |
while (server.isBound()) { | |
(new Thread(new ConnectionHandler(server.accept()))).start(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
class ConnectionHandler implements Runnable { | |
Socket client; | |
InputStream in; | |
OutputStream out; | |
public ConnectionHandler(Socket client) throws IOException { | |
this.client = client; | |
in = client.getInputStream(); | |
out = client.getOutputStream(); | |
} | |
@Override | |
public void run() { | |
BufferedReader reader = new BufferedReader(new InputStreamReader(in)); | |
try { | |
String line = reader.readLine(); | |
if (line != null && line.startsWith("GET")) { | |
System.out.println(client.getInetAddress() + " : " + line); | |
String req = URLDecoder.decode(FileServer.BASE_DIR + line.substring(4, line.indexOf(' ', 4)).replaceAll("/../", "/./")); | |
System.out.println(req); | |
HashMap<String, String> headers = new HashMap<>(); | |
while (!(line = reader.readLine()).isEmpty()) { | |
int div = line.indexOf(':'); | |
headers.put(line.substring(0, div), line.substring(div+1)); | |
} | |
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); | |
File request = new File(req); | |
if (!request.canRead()) { | |
System.out.println(request); | |
writer.write("HTTP/1.1 404 Not Found\r\n\r\n"); | |
writer.write("404 The requested URL was not found on this server.\r\n\r\n"); | |
writer.flush(); | |
} else { | |
if (request.isDirectory()) writeDirectory(request, writer); | |
else writeFile(request, out, writer, headers.get("Range")); | |
writer.close(); | |
} | |
} | |
else { | |
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); | |
writer.write("HTTP/1.1 501 Not Implemented\r\n\r\n"); | |
writer.write("501 Not Implemented\r\n\r\n"); | |
writer.flush(); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
try { | |
client.close(); | |
in.close(); | |
out.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
static void writeFile(File file, OutputStream out, BufferedWriter writer, String range) throws IOException { | |
long from = 0, to = 0; | |
if (range != null) { | |
range = range.trim(); | |
System.out.println(range); | |
if (!range.startsWith("bytes")) range = null; | |
else if (range.contains(",")) range = null; | |
else { | |
try { | |
String[] ranges = range.substring("bytes=".length()).split("-"); | |
if (range.charAt("bytes=".length()-1) == '-') { | |
ranges = new String[]{"0", ranges[0]}; //TODO this is not how it works, fixme | |
} | |
from = Long.parseLong(ranges[0]); | |
to = ranges.length > 2 && ranges[1].isEmpty() ? Long.parseLong(ranges[1]) : Files.size(file.toPath()); | |
assert from < to; | |
assert to <= Files.size(file.toPath()); | |
} catch (Exception e) { | |
range = null; | |
} | |
if (range != null) { | |
writer.write("HTTP/1.1 206 Partial Content\r\n"); | |
writer.write("Content-Type: " + Files.probeContentType(file.toPath()) + "\r\n"); | |
writer.write("Content-Length: " + (to - from) + "\r\n"); | |
writer.write("Content-Range: bytes " + to + "-" + from + "/" + Files.size(file.toPath()) + "\r\n"); | |
writer.write("Accept-Ranges: bytes\r\n"); | |
writer.write("Connection: close\r\n"); | |
writer.write("\r\n"); | |
} | |
} | |
} | |
if (range == null) { | |
// we do not support this. We can send a 200 OK according to https://tools.ietf.org/html/rfc2616#section-3.12 | |
writer.write("HTTP/1.1 200 OK\r\n"); | |
writer.write("Content-Type: " + Files.probeContentType(file.toPath()) + "\r\n"); | |
writer.write("Content-Length: " + (to = Files.size(file.toPath())) + "\r\n"); | |
writer.write("Accept-Ranges: bytes\r\n"); | |
writer.write("Connection: close\r\n"); | |
writer.write("\r\n"); | |
} | |
writer.flush(); | |
FileInputStream in = new FileInputStream(file); | |
long skipped = from; | |
while (skipped > 0) skipped -= in.skip(skipped); | |
long toTransfer = to-from; | |
int read; | |
byte[] buffer; | |
for(buffer = new byte[8192]; (read = in.read(buffer, 0, (int) Math.min(8192, toTransfer))) >= 0; toTransfer -= (long)read) { | |
out.write(buffer, 0, read); | |
} | |
out.flush(); | |
} | |
static void writeDirectory(File dir, BufferedWriter writer) throws IOException { | |
File[] listOfFiles = dir.listFiles(); | |
writer.write("HTTP/1.1 200 OK\r\n"); | |
writer.write("Content-Type: text/html; charset=UTF-8\r\n"); | |
writer.write("\r\n"); | |
writer.write("<!DOCTYPE html>\n" + | |
"<html>\n" + | |
"<head>\n" + | |
"\t<title>Index of " + dir.getAbsolutePath() + "</title>\n" + | |
"</head>\n" + | |
"<body>\n"); | |
if (listOfFiles == null || listOfFiles.length == 0) { | |
writer.write("No files found.\n"); | |
} else { | |
Arrays.sort(listOfFiles); | |
for (File f : listOfFiles) { | |
if (f.isFile()) { | |
writer.write("<a href=\"" + f.getPath().substring(FileServer.BASE_DIR.length()) + "\">" + f.getName() + "</a>"); | |
writer.write("<br>"); | |
} else if (f.isDirectory()) { | |
writer.write("<a href=\"" + f.getPath().substring(FileServer.BASE_DIR.length()) + "\">" + f.getName() + '/' + "</a>"); | |
writer.write("<br>"); | |
} | |
} | |
} | |
writer.write("<hr>"); | |
writer.write("</body>\n" + | |
"</html>\n"); | |
writer.write("\r\n\r\n"); | |
writer.flush(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment