Using Nanohttpd in Android

Using NanoHTTPD in Android

A late answer but may be useful to others.

Here is a simple hello Web Server, not exactly what you ask, but you can continue from here. The following program supposes you have a www directory in the root of the SD Card and a file index.html inside.

Main activity Httpd.java:

package com.inforscience.web;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import java.io.*;
import java.util.*;

public class Httpd extends Activity
{
private WebServer server;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
server = new WebServer();
try {
server.start();
} catch(IOException ioe) {
Log.w("Httpd", "The server could not start.");
}
Log.w("Httpd", "Web server initialized.");
}

// DON'T FORGET to stop the server
@Override
public void onDestroy()
{
super.onDestroy();
if (server != null)
server.stop();
}

private class WebServer extends NanoHTTPD {

public WebServer()
{
super(8080);
}

@Override
public Response serve(String uri, Method method,
Map<String, String> header,
Map<String, String> parameters,
Map<String, String> files) {
String answer = "";
try {
// Open file from SD Card
File root = Environment.getExternalStorageDirectory();
FileReader index = new FileReader(root.getAbsolutePath() +
"/www/index.html");
BufferedReader reader = new BufferedReader(index);
String line = "";
while ((line = reader.readLine()) != null) {
answer += line;
}
reader.close();

} catch(IOException ioe) {
Log.w("Httpd", ioe.toString());
}

return new NanoHTTPD.Response(answer);
}
}

}

Obviously the NanoHTTPD class must be in the same package.

You need to grant internet permission in AndroidManifest.xml.

<uses-permission android:name="android.permission.INTERNET" />

and read external storage permission.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

EDIT: To access the server open you web browser with the IP of your device, e.g. 192.168.1.20:8080.

NOTES:

  • Tested in Android 2.3
  • The use of port 80 is restricted to the root user(http://www.mail-archive.com/android-developers@googlegroups.com/msg47377.html).

working example of nanohttpd in android studio

Here's code from a fairly simple app that I wrote using NanoHTTPD.
It's called 'ClockServer' because I use it in a digital clock app, to allow the the clock's digit color, background color, and brightness to be set remotely.

I instantiate the ClockServer class in my app's Application subclass.
As you can see, the serve method returns it's result like so:

return new NanoHTTPD.Response(Response.Status.OK, MIME_JSON, responseString);

Here's the ClockServer class:

import android.content.res.Resources;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import fi.iki.elonen.NanoHTTPD;

public class ClockServer extends NanoHTTPD {

private static final String MIME_JSON = "application/json";
private Clock clock;

public ClockServer(Clock clock, int port) {
super(port);
this.clock = clock;
}

@Override
public Response serve(IHTTPSession session) {
try {
Method method = session.getMethod();
String uri = session.getUri();
Map<String, String> parms = session.getParms();
String responseString = serveClock(session, uri, method, parms);
return new NanoHTTPD.Response(Response.Status.OK, MIME_JSON, responseString);

} catch (IOException ioe) {
return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
} catch (ResponseException re) {
return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
} catch (NotFoundException nfe) {
return new NanoHTTPD.Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
} catch (Exception ex) {
return new Response(Response.Status.INTERNAL_ERROR, MIME_HTML, "<html><body><h1>Error</h1>" + ex.toString() + "</body></html>");
}
}

private String serveClock(IHTTPSession session, String uri, Method method, Map<String, String> parms) throws IOException, ResponseException {
String responseString = "";
do {
if(Method.GET.equals(method)) {
responseString = handleGet(session, parms);
break;
}

if(Method.POST.equals(method)) {
responseString = handlePost(session);
break;
}

throw new Resources.NotFoundException();

} while(false);

return responseString;
}

private String handleGet(IHTTPSession session, Map<String, String> parms) {
return clock.handleRequest("{'name':'status', 'value':''}");
}

private String handlePost(IHTTPSession session) throws IOException, ResponseException {
Map<String, String> files = new HashMap<String, String>();
session.parseBody(files);

return clock.handleRequest(files.get("postData"));
}

private class NotFoundException extends RuntimeException {
}
}

How to serve a file on sdcard using NanoHTTPD (inside Android)

Firstly, you need to make sure you set the mimetype properly when serving media files.

Secondly, you won't get very far reading a MP3 file line by line using FileReader, instead you should provide NanoHTTPD with an InputStream.

Below is a working modified version of your code, which serves a MP3 file. By setting the mimetype to audio/mpeg you let the browser decide what to do with this content. In Chrome, for example, the integrated music player is launched and plays the file.

public class StackOverflowMp3Server extends NanoHTTPD {

public StackOverflowMp3Server() {
super(8089);
}

@Override
public Response serve(String uri, Method method,
Map<String, String> header, Map<String, String> parameters,
Map<String, String> files) {
String answer = "";

FileInputStream fis = null;
try {
fis = new FileInputStream(Environment.getExternalStorageDirectory()
+ "/music/musicfile.mp3");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new NanoHTTPD.Response(Status.OK, "audio/mpeg", fis);
}
}

EDIT: a lot of people have been asking how to make the audio file seekable using range requests, I'll demonstrate this below

To make the audio file seekable, range requests are used which enable HTTP clients to retrieve parts of the audio file in chunks. Make sure that you're serving the file with the PARTIAL_CONTENT response status (HTTP 206). An example implementation can be found in the example code of NanoHTTPD: SimpleWebserver.java

In my implementation, instead of returning a NanoHTTPD Response directly in the serve method, I create another method called "servefile" which I use as the response for handling the range requests, as you can see below. This code is a modified implementation of the SimpleWebServer.java I linked above.

  @Override
public Response serve(String uri, Method method,
Map<String, String> header, Map<String, String> parameters,
Map<String, String> files) {

File f = new File(Environment.getExternalStorageDirectory()
+ "/music/musicfile.mp3");
String mimeType = "audio/mpeg";

return serveFile(uri, header, f, mimeType);
}
//Announce that the file server accepts partial content requests
private Response createResponse(Response.Status status, String mimeType,
InputStream message) {
Response res = new Response(status, mimeType, message);
res.addHeader("Accept-Ranges", "bytes");
return res;
}

/**
* Serves file from homeDir and its' subdirectories (only). Uses only URI,
* ignores all headers and HTTP parameters.
*/
private Response serveFile(String uri, Map<String, String> header,
File file, String mime) {
Response res;
try {
// Calculate etag
String etag = Integer.toHexString((file.getAbsolutePath()
+ file.lastModified() + "" + file.length()).hashCode());

// Support (simple) skipping:
long startFrom = 0;
long endAt = -1;
String range = header.get("range");
if (range != null) {
if (range.startsWith("bytes=")) {
range = range.substring("bytes=".length());
int minus = range.indexOf('-');
try {
if (minus > 0) {
startFrom = Long.parseLong(range
.substring(0, minus));
endAt = Long.parseLong(range.substring(minus + 1));
}
} catch (NumberFormatException ignored) {
}
}
}

// Change return code and add Content-Range header when skipping is
// requested
long fileLen = file.length();
if (range != null && startFrom >= 0) {
if (startFrom >= fileLen) {
res = createResponse(Response.Status.RANGE_NOT_SATISFIABLE,
NanoHTTPD.MIME_PLAINTEXT, "");
res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
res.addHeader("ETag", etag);
} else {
if (endAt < 0) {
endAt = fileLen - 1;
}
long newLen = endAt - startFrom + 1;
if (newLen < 0) {
newLen = 0;
}

final long dataLen = newLen;
FileInputStream fis = new FileInputStream(file) {
@Override
public int available() throws IOException {
return (int) dataLen;
}
};
fis.skip(startFrom);

res = createResponse(Response.Status.PARTIAL_CONTENT, mime,
fis);
res.addHeader("Content-Length", "" + dataLen);
res.addHeader("Content-Range", "bytes " + startFrom + "-"
+ endAt + "/" + fileLen);
res.addHeader("ETag", etag);
}
} else {
if (etag.equals(header.get("if-none-match")))
res = createResponse(Response.Status.NOT_MODIFIED, mime, "");
else {
res = createResponse(Response.Status.OK, mime,
new FileInputStream(file));
res.addHeader("Content-Length", "" + fileLen);
res.addHeader("ETag", etag);
}
}
} catch (IOException ioe) {
res = createResponse(Response.Status.FORBIDDEN,
NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
}

return res;
}

Using nanohttpd for server on localhost, how to serve the static HTML code in the whole directory?

Output:
Sample Image

LogCat:

2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: -------Assets List-----
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: asset-manifest.json
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: favicon.ico
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: index.html
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: manifest.json
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: precache-manifest.81af63d07b6dd6ae8e331187c522b020.js
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: service-worker.js
2019-08-05 15:21:53.838 10650-10650/com.neonxorg.neonx E/MainActivity: static
2019-08-05 15:21:53.842 10650-10650/com.neonxorg.neonx E/MainActivity: copyFolderFromAssets rootDirFullPath-www targetDirFullPath-/storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www
2019-08-05 15:21:53.865 10650-10650/com.neonxorg.neonx E/MainActivity: copyFolderFromAssets rootDirFullPath-www/static targetDirFullPath-/storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/static
2019-08-05 15:21:53.867 10650-10650/com.neonxorg.neonx E/MainActivity: copyFolderFromAssets rootDirFullPath-www/static/css targetDirFullPath-/storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/static/css
2019-08-05 15:21:53.922 10650-10650/com.neonxorg.neonx E/MainActivity: copyFolderFromAssets rootDirFullPath-www/static/js targetDirFullPath-/storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/static/js
2019-08-05 15:21:54.352 10650-10650/com.neonxorg.neonx E/MainActivity: copyFolderFromAssets rootDirFullPath-www/static/media targetDirFullPath-/storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/static/media
2019-08-05 15:21:54.526 10650-10650/com.neonxorg.neonx E/MainActivity: -------Root File List-----
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/precache-manifest.81af63d07b6dd6ae8e331187c522b020.js
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/service-worker.js
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/static
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/favicon.ico
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/manifest.json
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/asset-manifest.json
2019-08-05 15:21:54.528 10650-10650/com.neonxorg.neonx E/File: /storage/emulated/0/Android/data/com.neonxorg.neonx/cache/www/index.html
2019-08-05 15:21:54.704 10650-10650/com.neonxorg.neonx E/MainActivity: Connected : Please access! http://192.168.1.2:3000 From a web browser

Code:

public final String TAG = getClass().getSimpleName();
public void startLocalServer(int port, String root, Boolean localhost, Boolean keepAlive) {
try {
String[] filePathList = (getAssets().list("www"));
Log.e(TAG,"-------Assets List-----");
for (String s : filePathList) {
Log.e(TAG, s);
}
File externalCache = getExternalCacheDir();
if (externalCache != null) {
String path = externalCache.getAbsolutePath() + "/" + root;
copyFolderFromAssets(getApplicationContext(), "www", path);
File www_root = new File(path);
Log.e(TAG,"-------Root File List-----");
for (File f : www_root.listFiles()) {
Log.e("File ", f.getAbsolutePath());
}
server = new WebServer("localhost", port, www_root.getCanonicalFile());
server.start();
printIp();
}

} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}

public void copyFolderFromAssets(Context context, String rootDirFullPath, String targetDirFullPath) {
Log.e(TAG,"copyFolderFromAssets " + "rootDirFullPath-" + rootDirFullPath + " targetDirFullPath-" + targetDirFullPath);
File file = new File(targetDirFullPath);
if (!file.exists()) {
new File(targetDirFullPath).mkdirs();
}
try {
String[] listFiles = context.getAssets().list(rootDirFullPath);// 遍历该目录下的文件和文件夹
for (String string : listFiles) {// 看起子目录是文件还是文件夹,这里只好用.做区分了
if (isFileByName(string)) {// 文件
copyFileFromAssets(context, rootDirFullPath + "/" + string, targetDirFullPath + "/" + string);
} else {// 文件夹
String childRootDirFullPath = rootDirFullPath + "/" + string;
String childTargetDirFullPath = targetDirFullPath + "/" + string;
new File(childTargetDirFullPath).mkdirs();
copyFolderFromAssets(context, childRootDirFullPath, childTargetDirFullPath);
}
}
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}

public void copyFileFromAssets(Context context, String assetsFilePath, String targetFileFullPath) {
InputStream assestsFileInputStream;
try {
assestsFileInputStream = context.getAssets().open(assetsFilePath);
FileOutputStream fOS = new FileOutputStream(new File(targetFileFullPath));
int length = -1;
byte[] buf = new byte[1024];
while ((length = assestsFileInputStream.read(buf)) != -1) {
fOS.write(buf, 0, length);
}
fOS.flush();
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}

private boolean isFileByName(String str) {
return str.contains(".");
}

private void printIp() {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
final String formatedIpAddress = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
(ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
Log.e(TAG,"Connected : " + "Please access! http://" + formatedIpAddress + ":" + server.getListeningPort() + " From a web browser");
}

given path is not a directory.

When nanphttpd is not able to locate the data then it gives this error.

Why You are not getting the actual error

In the catch block of copyFolderFromAssets and copyFileFromAssets you are using e.printStackTrace() which may not be showing on your LogCat due to Show only selected application filter

Sample Image

In order to print the errors, you need to use the following:

Log.e(TAG, Log.getStackTraceString(e));

I replaced all of your System.out and e.printStackTrace with Log.e statements. Most likely the app wasn't able to copy contents from www directory to target directory. I changed target directory to cache directory and it worked on my device. (see below):

File externalCache = getExternalCacheDir();
if (externalCache != null) {
String path = externalCache.getAbsolutePath() + "/" + root;
File www_root = new File(path);
copyFolderFromAssets(getApplicationContext(), "www", path);
Log.e(TAG,"-------Root File List-----");
for (File f : www_root.listFiles()) {
Log.e("File ", f.getAbsolutePath());
}
server = new WebServer("localhost", port, www_root.getCanonicalFile());
server.start();
printIp();
}

Sidenote:

  • There is no need of using static keyword in those function until or unless you want to copy them in a utility class


Related Topics



Leave a reply



Submit