Skip to content

Instantly share code, notes, and snippets.

@pranavgade20
Last active December 22, 2020 07:17
Show Gist options
  • Save pranavgade20/04943bb9c91e0b7e6cf3a598ecc74212 to your computer and use it in GitHub Desktop.
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.
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