1

My goal was to create a Java service on Windows that runs locally and communicates with local programs using JSON. The service should be reliable and robust.

The service runs a ServerSocket and receives receives and responds to JSON messages. The following is a skeleton, where appropriate "commands" will be added to the SigningController class. The service will likely stay small-scale, and will contain ~10 different commands.

Questions

Does the server seem reliable (i.e. no potential hangups, exceptions etc)?

I've noticed that after heavy usage (sending lots of "echo" commands with large strings), the memory usage builds up to ~530mb, and then slowly drops down. Do I have a resource or memory leak or is that standard JVM behavior?

Maybe off-topic, but is there a way to change the task name and icon which show up in Task Manager? Currently the process shows up as "Java(TM) Platform SE binary".

The service is installed with nssm and the bat files

install.bat (as administrator)

@echo off
pushd %~dp0
nssm.exe stop JavaSigningService
nssm.exe remove JavaSigningService confirm
nssm.exe install JavaSigningService %~dp0start.bat
nssm.exe start JavaSigningService
popd
pause

start.bat

java -Xmx512m -jar signingservice.jar

POM file and classes below.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>Server.Main</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <finalName>signingservice</finalName>
                    <appendAssemblyId>false</appendAssemblyId>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <modelVersion>4.0.0</modelVersion>
    <groupId>...</groupId>
    <artifactId>signingservice</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
    </dependencies>

</project>

Main

package Server;

import java.net.ServerSocket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    public static void main(String[] args) {
        int port = Integer.parseInt(args[0]);
        try (ServerSocket listener = new ServerSocket(port)) {
            System.out.println("Server is running...");
            ExecutorService pool = Executors.newFixedThreadPool(10);
            while (true) {
                try {
                    pool.execute(new Signer(listener.accept()));
                } catch (Exception e){
                    // Log
                }
            }
        } catch (Exception e) {
            // Log
        } finally {
            System.exit(0);
        }
    }
}

Utils

package Server;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.PrintWriter;
import java.io.StringWriter;

public class Utils {
    private static JsonParser parser = new JsonParser();

    public static JsonObject getJsonFromString(String str) throws Exception{
        JsonObject jsonObject = parser.parse(str).getAsJsonObject();
        return jsonObject;
    }
    public static String getJsonProperty(JsonObject json, String property){
        String value = null;
        try {
            value = json.get(property).getAsString();
        } catch (Exception e) {}
        return value;
    }

    public static JsonObject errorMessageTemplate(Exception e){
        JsonObject result = new JsonObject();
        result.addProperty("result", "ERROR");
        result.addProperty("errormessage", e.getMessage());
        result.addProperty("stacktrace", stackTraceAsString(e));
        return result;
    }

    public static String stackTraceAsString(Exception e){
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        return sw.toString();
    }
}

SigningController

package Server;

import com.google.gson.JsonObject;

public class SigningController {
    public static JsonObject handle(JsonObject received) throws Exception{
        String command = Utils.getJsonProperty(received, "command");
        if("echo".equals(command)){
            return simpleEcho(received);
        }
        return Utils.errorMessageTemplate(new Exception("Command not specified or unknown."));
    }

public static JsonObject simpleEcho(JsonObject received) throws Exception{
    String data = "" + Utils.getJsonProperty(received, "data");
    JsonObject result = new JsonObject();
    result.addProperty("result", "OK");
    result.addProperty("data", data);
    return result;
}
}

Signer

package Server;

import com.google.gson.JsonObject;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Signer implements Runnable{
    private Socket socket;
    private BufferedReader socketIn;
    private PrintWriter socketOut;

    Signer(Socket socket) throws IOException {
        this.socket = socket;
        this.socket.setSoTimeout(60*1000); // read timeout in milliseconds
        socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
        socketOut = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
    }

    @Override
    public void run() {
        System.out.println("Connected: " + socket);
        try {
            socketOut.println("hello");
            String line = socketIn.readLine();
            JsonObject json = Utils.getJsonFromString(line);
            JsonObject result = SigningController.handle(json);
            socketOut.println(result);
        } catch (Exception e){
            // Log
            System.out.println("Error: " + socket);
            e.printStackTrace();
            try {
                socketOut.println(Utils.errorMessageTemplate(e));
            } catch (Exception e1) {}
        } finally{
            // Cleanup
            try{
                socketIn.close();
            } catch (IOException e){
                e.printStackTrace();
            }
            try{
                socketOut.close();
            } catch (Exception e){
                e.printStackTrace();
            }
            try{
                socket.close();
                System.out.println("Closed: " + socket);
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

0 Answers0