I have an application written in JavaFX to control some lights in a theater with a very simple interface. Basically two buttons, one to fade lights up over 3 seconds and the other to fade them down over 3 seconds. The app connects to an Ethernet to Serial Server (Sealevel Sealink 4104) to control the lights.
I would like to add a browser interface so that the app can be controlled via any mobile device. I have added a Java web server based on code I got from this video.
https://www.youtube.com/watch?v=G4Z2PQfOHdY
The app runs, and I can get the web page I am looking for in the browser. However, my app interface never shows up. The idea is that the app interface is always present to indicate that it is running. The web page interface would be available to extend the control options to a mobile device.
The main question at this point is how do I get the web server to run in the background without interfering with the functioning of the app interface?
The web server code:
package lightcontrol2;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;
public final class JavaWebserver {
public final void StartServer() throws Exception {
// Set port number.
int port = 9000;
// Establish the listening socket.
ServerSocket serverSocket = new ServerSocket(port);
// Process HTTP sevice requests in an infinite loop.
while (true) {
// Listen for TCP connection request.
Socket connectionSocket = serverSocket.accept();
// Construct an object to process the HTTP request message.
HttpRequest request = new HttpRequest(connectionSocket);
// Create a new thread to process the request.
Thread thread = new Thread(request);
// Start the thread.
thread.start();
}
}
}
final class HttpRequest implements Runnable {
// Return carriage return (CR) and line feed (LF).
final static String CRLF = "\r\n";
Socket socket;
// Constructor.
public HttpRequest(Socket socket) throws Exception {
this.socket = socket;
}
// Implement the run() method of the Runnable interface.
// Within run(), explicitly catch and handle exceptions
// with try/ catch block.
@Override
public void run() {
try {
processRequest();
} catch (Exception e){
System.out.println(e);
}
}
private void processRequest() throws Exception {
// Get a reference to the socket's input and output streams.
InputStream instream = socket.getInputStream();
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
// Set up input stream filters.
// Page 169, 10th line down or so . . .
// Reads the input data.
BufferedReader br = new BufferedReader(new InputStreamReader(instream));
// Get the request line of the HTTP request message.
// Get path/file.html version of http
String requestLine = br.readLine();
// Display the request line.
System.out.println();
System.out.println(requestLine);
// Deal with the request.
// Extract the filename from the request line.
// This is an input method with deliminators.
StringTokenizer tokens = new StringTokenizer(requestLine);
// Skip over the method, which should be 'GET'.
tokens.nextToken();
String fileName = tokens.nextToken();
// Root of the server.
String root = "/www/";
fileName = root + fileName;
// Open the requested file.
FileInputStream fis = null;
boolean fileExists = true;
try {
fis = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
fileExists = false;
}
// Construct the response message.
String statusLine = null;
String contentTypeLine = null;
String entityBody = null;
if (fileExists) {
statusLine = "HTTP/1.0 200 OK" + CRLF;
contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
}
else {
statusLine = "HTTP/1.0 404 Not Found" + CRLF;
contentTypeLine = "Content-type: " + "text/html" + CRLF;
entityBody = "<HTML>" +
"<HEAD><TITLE>Not Found</TITLE></HEAD>" +
"<BODY>NOt Found</BODY></HTML>";
}
//Send the status line.
os.writeBytes(statusLine);
// Sent the content type line.
os.writeBytes(contentTypeLine);
// Send a blank line to indicate the end of the header lines.
os.writeBytes(CRLF);
// Send the entity body.
if (fileExists) {
sendBytes(fis, os);
os.writeBytes(statusLine);
fis.close();
} else {
os.writeBytes(statusLine);
os.writeBytes(entityBody);
os.writeBytes(contentTypeLine);
}
System.out.println("*****");
System.out.println(fileName);
System.out.println("*****");
// Get and display the header lines.
String headerLine = null;
while ((headerLine = br.readLine()).length() != 0) {
System.out.println(headerLine);
}
// Close streams and socket.
os.close();
br.close();
socket.close();
}
private static String contentType(String fileName) {
if (fileName.endsWith(".htm") || fileName.endsWith(".html")) {
return "text/html";
}
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
return "image/jpeg";
}
if (fileName.endsWith(".gif")) {
return "image/gif";
}
return "application/octet-stream";
}
private static void sendBytes(FileInputStream fis, OutputStream os) throws Exception {
// Construct 1K buffer to hold bytes on way to the socket.
byte[] buffer = new byte[1024];
int bytes = 0;
// Copy requested file into the socket's output stream.
// read() returns -1, indicating end of file.
while ((bytes = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytes);
}
}
}
Here is the interface code:
package lightcontrol2;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class LightControl2 extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = createGrid();
SealinkConnect connect = new SealinkConnect();
JavaWebserver webserver = new JavaWebserver();
Button btnOn = new Button();
grid.add(btnOn, 0, 1);
btnOn.setText("3 Sec On");
btnOn.setOnAction((ActionEvent event) -> {
System.out.println("3N:100:A");
connect.sendCommand("3N:100:A");
});
Button btnOff = new Button();
grid.add(btnOff, 0, 2);
btnOff.setText("3 Sec Off");
btnOff.setOnAction((ActionEvent event) -> {
System.out.println("3F:A");
connect.sendCommand("3F:A");
});
BorderPane root = new BorderPane();
root.setPadding(new Insets(10));
root.setCenter(grid);
Scene scene = new Scene(root, 365, 300);
primaryStage.setTitle("Light Control Test");
primaryStage.setScene(scene);
scene.getStylesheets().add
(LightControl2.class.getResource("style.css").toExternalForm());
primaryStage.show();
connect.socketConnect();
webserver.StartServer();
}
private GridPane createGrid() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(5);
grid.setVgap(10);
grid.setPadding(new Insets(10));
return grid;
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I'm going to guess that JavaFX needs its thread back. It invokes start()
, in which you call webserver.StartServer()
, which in turns remains stuck in an infinite while(true)
loop. You should do the socket accepting loop in a separate thread as well (and shut it down properly as needed) and let the start
method return.
That being said, I would not recommend trying to implement a pseudo-HTTP-server on your own - that's just extra code, work and maintenance and might break in various ways if it is not RFC-compliant. There are plenty of embeddable lightweight HTTP servers you can use. As the author of JLHTTP, I think it could be a good match for your use case, but there are many others to choose from.
Using JLHTTP 2.1, you'd need something like this:
public void startWebServer() {
String dir = "."; // local folder to serve website files from
HTTPServer server = new HTTPServer(9000); // pick a port, any port
HTTPServer.VirtualHost host = server.getVirtualHost(null); // default virtual host
host.addContext("/", new FileContextHandler(new File(dir), "/")); // serve website files from disk directory
host.addContext("/api/lights", (request, response) -> {
Map<String, String> params = request.getParams();
String action = params.get("action");
if (action == null)
action = "";
switch (action) {
case "on":
connect.sendCommand("3N:100:A");
return 200; // ok
case "off":
connect.sendCommand("3F:A");
return 200; // ok
default:
return 400; // bad request
}
}, "GET", "POST"); // support both GET and POST requests
server.start();
}
Notes: