Skip to content

Instantly share code, notes, and snippets.

@gjb2048
Created July 1, 2013 13:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gjb2048/5900577 to your computer and use it in GitHub Desktop.
Save gjb2048/5900577 to your computer and use it in GitHub Desktop.
Java JNI with C on a Raspberry Pi
/*
* FreeSpace JNI class.
* © G J Barnard 2013 - Attribution-NonCommercial-ShareAlike 3.0 Unported - http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB.
*
* Defines the native functions to use and loads the resulting 'libfreespace.so' for Java to use at runtime.
*
* Start here.
* So with this file operating above the package folder:
* javac miniwebserver/FreeSpace.java to generate the .class file for FreeSpaceMiniWebServer.
* javah miniwebserver.FreeSpace to generate the miniwebserver_FreeSpace.h file with the function declarations to implement.
* Then look at the instructions in miniwebserver_FreeSpace.c.
*
* Useful websites:
* http://jonisalonen.com/2012/calling-c-from-java-is-easy/
* http://www.steveolyo.com/JNI/JNI.html
*/
package miniwebserver;
/**
* @author G J Barnard
*/
public class FreeSpace
{
// Load the C dynamic 'so' library which will contain the implemented 'C' functions....
static
{
System.loadLibrary("freespace");
}
// C functions - can be implemented in C++....
public static native String getFreeSpace(String device);
}
/*
* Free Space Mini web server.
* © G J Barnard 2013 - Attribution-NonCommercial-ShareAlike 3.0 Unported - http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB.
*/
package miniwebserver;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;
/**
* @author G J Barnard
*/
public class FreeSpaceMiniWebServer implements HttpHandler
{
private MimeTypeFactory mimeTypeFactory = new MimeTypeFactory();
public static final int BUFFER_MAX = 65536 - 40; // Max packet size less TCP/IP headers etc. Use WireShark (http://www.wireshark.org/) to see what I mean.
private static String userdir = System.getProperty("user.dir");
private static String MWS_TITLE = "FreeSpaceMiniWebServer";
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
Thread.setDefaultUncaughtExceptionHandler(new FreeSpaceMiniWebServer.freeSpaceMiniWebServerExceptionHandler());
FreeSpaceMiniWebServer us = new FreeSpaceMiniWebServer();
}
/**
* Constructor
*/
public FreeSpaceMiniWebServer()
{
System.out.println("Free Space Mini Web Server");
System.out.println("User directory is: " + userdir);
createServer();
}
/**
* Destructor.
* @param exitCode The exit code.
*/
public static void destruct(int exitCode)
{
System.exit(exitCode);
}
/**
* Should execute in response to a Ctrl-C etc.
*/
protected static class freeSpaceMiniWebServerExceptionHandler implements Thread.UncaughtExceptionHandler
{
@Override
public void uncaughtException(Thread t,
Throwable e)
{
System.out.println("Free Space Mini Web Server Java VM Exception: " + e.getMessage() + " from " + t.getName());
System.out.println("Need to quit!");
destruct(-1);
}
}
/**
* Create the web server on the given port serving the specified context.
*/
private void createServer()
{
int httpPort = 8084;
InetSocketAddress address = new InetSocketAddress(httpPort);
try
{
HttpServer theServer = HttpServer.create(address, 0);
theServer.createContext("/", this);
createThreads(theServer); // Try without to see the difference of not having multiple threads to serve the requests.
theServer.start();
String hostname = InetAddress.getLocalHost().getHostName();
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null)
{
while (interfaces.hasMoreElements())
{
NetworkInterface ni = interfaces.nextElement();
Enumeration<InetAddress> interfaceAddresses = ni.getInetAddresses();
while (interfaceAddresses.hasMoreElements())
{
InetAddress interfaceAddress = interfaceAddresses.nextElement();
System.out.println("Accepting requests on " + hostname + ", interface " + ni.getDisplayName() + " - " + interfaceAddress.getHostAddress() + ":" + httpPort);
}
}
}
else
{
System.out.println("Free Space Mini Web Server - No known network interfaces.");
}
}
catch (IOException e)
{
e.printStackTrace(System.err);
}
}
/**
* Setup the thread and queue creation classes.
*
* @param theServer The server that will receive with the requests.
*/
private void createThreads(HttpServer theServer)
{
int corePoolSize = 20;
int maximumPoolSize = 40;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
int workQueueCapacity = 40;
int threadPriority = Thread.NORM_PRIORITY;
System.out.println(MWS_TITLE + ":createThreads - Creating thread factory with a thread priority of: " + threadPriority);
MiniWebServerThreadFactory threadFactory = new MiniWebServerThreadFactory(threadPriority);
System.out.println(MWS_TITLE + ":createThreads - Creating thread pool executor with:");
System.out.println("Core Pool Size : " + corePoolSize);
System.out.println("Maximum Pool Size : " + maximumPoolSize);
System.out.println("Keep Alive Time : " + keepAliveTime + " seconds.");
System.out.println("Work Queue Capacity: " + workQueueCapacity);
MiniWebServerThreadPoolExecutor threadExecutor =
new MiniWebServerThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueueCapacity,
threadFactory);
theServer.setExecutor(threadExecutor);
}
/**
* Handle the HTTP request.
*
* @param request Request to process.
* @throws IOException If error.
*/
@Override
public void handle(HttpExchange request) throws IOException
{
long handleTimeNS = System.nanoTime();
long handleTimeMS = System.currentTimeMillis();
processRequest(request);
handleTimeNS = System.nanoTime() - handleTimeNS;
handleTimeMS = System.currentTimeMillis() - handleTimeMS;
System.out.println(request.getRequestURI().toString() + " took " + handleTimeNS + "ns or " + handleTimeMS + "ms.");
}
/**
* Process the HTTP request.
* @param request The request.
*/
private void processRequest(HttpExchange request)
{
// TODO: Process specific URL's, GET and POST requests.
URI dUrl = request.getRequestURI();
String rawPath = dUrl.getRawPath();
if (rawPath.length() > 1)
{
sendFile(request, rawPath);
}
else if (rawPath.charAt(0) == '/')
{
sendIndex(request);
}
else
{
// Bogus request...
Headers responseHeaders = request.getResponseHeaders();
responseHeaders.set("Date", new Date().toString());
responseHeaders.set("Server", MWS_TITLE);
try
{
request.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, 0);
}
catch (IOException ex)
{
System.err.println(MWS_TITLE + ":processRequest - Bad request.");
ex.printStackTrace(System.err);
}
request.close();
}
}
/**
* Send the 'index' file which is actually just the context of '/'.
* @param request
*/
private void sendIndex(HttpExchange request)
{
System.out.println(MWS_TITLE + ":sendIndex - " + request.getRequestURI().toString());
Headers responseHeaders = request.getResponseHeaders();
responseHeaders.set("Content-type", mimeTypeFactory.HTML.getType());
responseHeaders.set("Date", new Date().toString());
responseHeaders.set("Server", MWS_TITLE);
try
{
String contents = "<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"fsmws.css\"><title>Free Space Mini Web Server</title></head><body><h1>" + FreeSpace.getFreeSpace("/") + "</h1></body></html>";
request.sendResponseHeaders(HttpURLConnection.HTTP_OK, contents.length());
try (OutputStream os = request.getResponseBody())
{
os.write(contents.getBytes());
os.flush();
}
request.close();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
/**
* Send the given file to the web browser with the correct MIME type. Looks in the users directory for the file.
*
* @param request The request this is for.
* @param file The file to send.
*/
private void sendFile(HttpExchange request, String file)
{
String fullname = userdir + file;
System.out.println(MWS_TITLE + ":sendFile - " + request.getRequestURI().toString() + " - " + fullname);
File theFile = new File(fullname);
if (theFile.exists())
{
MimeType theType = mimeTypeFactory.guessContentTypeFromName(file);
byte[] fileContents = getFile(theFile);
if (fileContents != null)
{
Headers responseHeaders = request.getResponseHeaders();
responseHeaders.set("Date", new Date().toString());
responseHeaders.set("Server", MWS_TITLE);
responseHeaders.set("Content-type", theType.getType());
if (theType.isBinary())
{
try
{
// Info on http://elliotth.blogspot.com/2009/03/using-comsunnethttpserver.html
responseHeaders.set("Content-Encoding", "gzip");
request.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0); // Don't know how large the compressed file will be.
try (GZIPOutputStream gos = new GZIPOutputStream(request.getResponseBody(), BUFFER_MAX))
{
gos.write(fileContents);
gos.finish();
}
catch (Exception ex)
{
System.err.println(MWS_TITLE + ":sendFile - Binary file: " + file);
ex.printStackTrace(System.err);
}
}
catch (IOException ex)
{
System.err.println(MWS_TITLE + ":sendFile - Binary file: " + file);
ex.printStackTrace(System.err);
}
}
else
{
try
{
request.sendResponseHeaders(HttpURLConnection.HTTP_OK, fileContents.length);
try (OutputStream os = request.getResponseBody())
{
os.write(fileContents);
os.flush();
}
catch (Exception ex)
{
System.err.println(MWS_TITLE + ":sendFile - Text file: " + file);
ex.printStackTrace(System.err);
}
}
catch (IOException ex)
{
System.err.println(MWS_TITLE + ":sendFile - Text file: " + file);
ex.printStackTrace(System.err);
}
}
request.close();
}
else
{
// File has no content...
try
{
// File does not exist.
request.sendResponseHeaders(HttpURLConnection.HTTP_NO_CONTENT, 0);
request.close();
}
catch (IOException ex)
{
System.err.println(MWS_TITLE + ":sendFile - Sending response headers for file: " + file);
ex.printStackTrace(System.err);
}
System.err.println(MWS_TITLE + ":sendFile - File '" + file + "' has no content.");
}
}
else
{
// Code above has not got the file...
try
{
// File does not exist.
request.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, 0);
request.close();
}
catch (IOException ex)
{
System.err.println(MWS_TITLE + ":sendFile - Sending response headers for file: " + file);
ex.printStackTrace(System.err);
}
System.err.println(MWS_TITLE + ":sendFile - File '" + file + "' not sent.");
}
}
/**
* Gets the given file from the operating system.
*
* @param theFile The file to get.
* @return The file as a byte array.
*/
private byte[] getFile(File theFile)
{
byte[] buffer = null;
FileInputStream fis = null;
try
{
fis = new FileInputStream(theFile);
FileChannel fc = fis.getChannel();
long len = theFile.length();
int byteReadCount = 0;
ByteBuffer byteBuffer = ByteBuffer.allocate((int) len);
int bytesRead = 0;
while ((bytesRead != -1) && (byteReadCount < len))
{
bytesRead = fc.read(byteBuffer);
byteReadCount += bytesRead; // Cope with 0 return when have reached end of text file and cannot get -1 byte as buffer is full.
}
buffer = byteBuffer.array();
}
catch (FileNotFoundException ex)
{
ex.printStackTrace(System.err);
}
catch (IOException ex)
{
ex.printStackTrace(System.err);
}
catch (Exception ex)
{
ex.printStackTrace(System.err);
}
finally
{
if (fis != null)
{
try
{
fis.close();
}
catch (IOException ex)
{
ex.printStackTrace(System.err);
}
}
}
return buffer;
}
/**
* Factory to create the threads to serve the requests.
*/
private class MiniWebServerThreadFactory implements ThreadFactory
{
private ThreadGroup miniWebServerThreadGroup = new ThreadGroup("Free Space Mini Web Server Thread Group");
private int threadPriority = Thread.NORM_PRIORITY;
private int threadCount = 0;
/**
* Constructor
*
* @param threadPriority The priority all threads are created with.
*/
public MiniWebServerThreadFactory(int threadPriority)
{
this.threadPriority = threadPriority;
}
/**
* Creates a new thread.
*
* @param r The runnable task the thread is created for.
* @return A new thread.
*/
@Override
public Thread newThread(Runnable r)
{
Thread theThread = new Thread(miniWebServerThreadGroup, r, "FSMWS HTTP Server Thread-" + ++threadCount);
theThread.setPriority(threadPriority);
return theThread;
}
/**
* Gets the thread group that all leader board threads belong to.
*
* @return The thread group.
*/
public ThreadGroup getLeaderBoardThreadGroup()
{
return miniWebServerThreadGroup;
}
/**
* Gets all the current threads.
*
* @return The threads.
*/
public Thread[] getThreads()
{
Thread[] theThreads = new Thread[miniWebServerThreadGroup.activeCount()];
int threads = miniWebServerThreadGroup.enumerate(theThreads);
System.out.println(MWS_TITLE + "ThreadFactory:getThreads - " + threads + " active threads returned.");
return theThreads;
}
/**
* Sets the thread priority for all new threads.
*
* @param threadPriority The thread priority.
*/
public void setThreadPriority(int threadPriority)
{
// TODO: Set thread priority of all existing threads too.
this.threadPriority = threadPriority;
}
}
/**
* Executor to manage what thread will serve an incoming request.
*/
private class MiniWebServerThreadPoolExecutor extends ThreadPoolExecutor
{
/**
* See http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html#ThreadPoolExecutor(int,
* int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue, java.util.concurrent.ThreadFactory)
*
* @param corePoolSize
* @param maximumPoolSize
* @param keepAliveTime
* @param unit
* @param workQueueCapacity
* @param threadFactory
*/
public MiniWebServerThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
int workQueueCapacity,
ThreadFactory threadFactory)
{
super(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
new MiniWebServerBlockingQueue<Runnable>(workQueueCapacity),
threadFactory);
}
}
@SuppressWarnings("hiding")
private class MiniWebServerBlockingQueue<Runnable>
extends LinkedBlockingQueue<Runnable>
{
private static final long serialVersionUID = -3943671634425379250L;
/**
* Constructor
*
* @param capacity Capacity of the queue.
*/
public MiniWebServerBlockingQueue(int capacity)
{
super(capacity);
}
}
/**
* Class to store and determine MIME types - RFC2616 Section 19.4.
*/
private class MimeType
{
private boolean isBinary;
private String type;
public static final String HTML_STR = "text/html";
public static final String JSON_STR = "application/json";
public static final String TEXT_STR = "text/plain";
/**
* Constructor
*
* @param isBinary States if we are a binary type.
* @param type The type we are.
*/
public MimeType(boolean isBinary, String type)
{
this.isBinary = isBinary;
this.type = type;
}
/**
* States if we are a binary type.
*
* @return true or false.
*/
public boolean isBinary()
{
return isBinary;
}
/**
* States the MIME type.
*
* @return The type.
*/
public String getType()
{
return type;
}
}
/**
* Factory to create MimeType's.
*/
private class MimeTypeFactory
{
public MimeType HTML = new MimeType(false, MimeType.HTML_STR);
public MimeType JSON = new MimeType(false, MimeType.JSON_STR);
public MimeType TEXT = new MimeType(false, MimeType.TEXT_STR);
/**
* Try to guess the content type from the filename.
*
* @param name Name of the file to guess.
* @return Guessed MIME type text.
*/
public MimeType guessContentTypeFromName(String name)
{
if (name.endsWith(".html") || name.endsWith(".htm"))
{
return new MimeType(false, MimeType.HTML_STR);
}
else if (name.endsWith(".txt") || name.endsWith(".java"))
{
return new MimeType(false, "text/plain");
}
else if (name.endsWith(".gif"))
{
return new MimeType(true, "image/gif");
}
else if (name.endsWith(".class"))
{
return new MimeType(true, "application/octet-stream");
}
else if (name.endsWith(".jpg") || name.endsWith(".jpeg"))
{
return new MimeType(true, "image/jpeg");
}
else if (name.endsWith(".js"))
{
return new MimeType(false, "text/javascript; charset=UTF-8");
}
else if (name.endsWith(".css"))
{
return new MimeType(false, "text/css");
}
else if (name.endsWith(".png"))
{
return new MimeType(true, "image/png");
}
else if (name.endsWith(".otf"))
{
//return new MimeType(true, "vnd.oasis.opendocument.formula-template"); // http://www.iana.org/assignments/media-types/application/vnd.oasis.opendocument.formula-template
return new MimeType(true, "font/opentype"); // Chrome 'no winge' with this.
}
else if (name.endsWith(".ico"))
{
return new MimeType(true, "image/vnd.microsoft.icon");
}
else if (name.endsWith(".swf"))
{
return new MimeType(true, "application/x-shockwave-flash");
}
else
{
return new MimeType(false, MimeType.HTML_STR);
}
}
}
}
/*
* FreeSpace JNI C implementation.
* © G J Barnard 2013 - Attribution-NonCommercial-ShareAlike 3.0 Unported - http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB.
*/
@charset "UTF-8";
body {
font-family: sans-serif;
background-color: lightgoldenrodyellow;
text-align: center;
}
/*
* FreeSpace JNI C implementation.
* © G J Barnard 2013 - Attribution-NonCommercial-ShareAlike 3.0 Unported - http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB.
*
* When running standalone, pass in filepath, i.e. '/'.
*
* Using Java7 on user account 'demo' with a sub folder of 'Java' so adjust the following as required.
* Include the JNI headers etc.: export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-armhf
* Compile to object file: gcc -fPIC -c miniwebserver_FreeSpace.c -I $JAVA_HOME/include
* Compile to library: gcc miniwebserver_FreeSpace.o -shared -o libfreespace.so -Wl,-soname,freespace
* Compile the Java class that uses FreeSpace which loads libfreespace.so: javac miniwebserver/FreeSpaceMiniWebServer.java
* Tell the system where to find the libfreespace.so dynamic library: export LD_LIBRARY_PATH=/home/demo/Java
* Run the program: java miniwebserver/FreeSpaceMiniWebServer
*/
#include "miniwebserver_FreeSpace.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
int calcfreespace(const char *device);
char* displaydetails();
char* displayfreespace();
int exitcode;
char result[1024];
char hostname[128];
struct stat sbuf;
struct statfs buf;
char* thedevice;
extern char* ctime();
main(argc, argv)
int argc;
char **argv;
{
if (argc != 2) {
fprintf(stderr, "usage: us filesys-name\n");
exitcode = 1;
}
else if (calcfreespace(argv[1]) == 0)
{
printf(displaydetails());
printf("\n");
printf(displayfreespace());
printf("\n");
}
else
{
printf(result);
printf("\n");
}
exit(exitcode);
}
int calcfreespace(const char *device)
{
if (stat(device, &sbuf) < 0) {
sprintf(result, "stat error");
exitcode = 2;
} else {
if (statfs(device, &buf) < 0) {
sprintf(result, "statfs error, errno is: %d", errno);
exitcode = 2;
} else {
hostname[127] = '\0';
gethostname(&hostname, 127);
exitcode = 0;
thedevice = device;
}
}
return exitcode;
}
char* displaydetails()
{
if (exitcode == 0)
{
sprintf(result, "%s:\tblocks %u\tfree blocks %u\tblock size %u\tavailable space %u\tfree inodes %u", thedevice, buf.f_blocks, buf.f_bfree, sbuf.st_blksize, buf.f_bavail, buf.f_ffree);
}
return result;
}
char* displayfreespace()
{
unsigned long freebytes;
if (exitcode == 0)
{
long timevalue;
time(&timevalue);
freebytes = buf.f_bavail * sbuf.st_blksize;
sprintf(result, "Available space on %s %lu bytes %luK %fMB %fGB at %s on %s", thedevice, freebytes, (freebytes) / 1024, ((freebytes) / 1024.0) / 1024.0, (((freebytes) / 1024.0) / 1024.0) / 1024.0, ctime(&timevalue), hostname);
}
return result;
}
JNIEXPORT jstring JNICALL Java_miniwebserver_FreeSpace_getFreeSpace
(JNIEnv *env, jclass cls, jstring device)
{
if (calcfreespace((*env)->GetStringUTFChars(env, device, 0)) == 0)
{
return (*env)->NewStringUTF(env, displayfreespace());
}
else
{
return (*env)->NewStringUTF(env, result);
}
}
@zdanek
Copy link

zdanek commented Oct 29, 2014

Hi!
Thank you for the tutorial. It's the first one that explained everything step by step.

@gjb2048
Copy link
Author

gjb2048 commented Feb 23, 2015

Thank you zdanek, sorry for the delay in replying as was not notified of the comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment