I am trying to use NanoHTTP to serve up an HTML file. However, NanoHTTP is relatively un-documented, and I am new to Android. My question is, where do I store the html file, and how specifically can I serve it up using NanoHTTP.
-
2Unless this is some existing Android port of NanoHTTPD, you have some work ahead of you, not something I would recommend for somebody "new to Android". If it *is* some existing Android port of NanoHTTPD, the authors of that port hopefully provided some means for you to specify the document root via a `File`. – CommonsWare Jan 13 '13 at 23:55
-
4well, I am new to Android, but I am not new to Java or networking. All I need is a little push in the right direction. – Zwade Jan 14 '13 at 00:06
6 Answers
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).
-
1Thanks for posting this, I should have added my own code once I figured it out (which took a while). – Zwade Sep 08 '13 at 18:46
-
3Adding my thanks. Seriously, very helpful. This is the first nanohttpd example I've found that actually works with the current nanohttpd. My process: Create a new Android application project, paste in your main activity (changing the package name and activity name to match my project), copy in the NanoHTTPD.java file (from nanohttpd-master/core/src/main/java/fi/iki/elonen/ in the current https://github.com/NanoHttpd/nanohttpd download), add the INTERNET permission, create the www/index.html file, and that was it. Boom. Ran the app on my Note II and viewed the webpage from my PC. Works! – Andrew Cottrell Oct 07 '13 at 17:58
-
1You are welcome. I used a server for a college project(https://bitbucket.org/rendon/smsserver) and NanoHTTPD did the work so well. – rendon Oct 09 '13 at 16:19
-
@AndrewCottrell "and viewed the webpage from my PC. Works" How do you communicate between webpage and android activity? – Rahul Matte Mar 27 '15 at 13:14
-
Thanks, works on Lollipop. I only had to change R.layout.main to R.layout.activity_main – David Apr 08 '15 at 11:12
-
Can you comment on how to use the SSL support in NanoHTTPD? The docs claim it's in a separate branch I cannot find, but I found a message exchange suggesting it has been merged into the master branch already. – Michael Jun 27 '15 at 18:42
-
@Michael I'd like to, but it's been a long time since I used NanoHTTPD, I didn't use SSL in my application, so I don't know about the subject :(. – rendon Jun 29 '15 at 03:49
-
You should integrate the server in a `Service` and not in an `Activity`. Otherwise you have to deal with closed connections on device rotation etc. – artkoenig Jul 07 '15 at 10:06
-
if nanohttpd has a file server, why are you opening the string and reading/serving it yourself? Your version also has a huge memory overhead for large files while the native natnohttpd version advertises zero memory overhead. – Martin Konecny Feb 26 '17 at 00:05
-
@MartinKonecny I shared this for didactic purposes, it's basically a Hello World, not intended to run in production :). – rendon Mar 03 '17 at 21:48
-
-
-
-
Pretty good source code can be found here: https://github.com/Teaonly/android-eye
Chceck assets folder where html and JavaScript files are stored https://github.com/Teaonly/android-eye/tree/master/assets
TeaServer - server implementation https://github.com/Teaonly/android-eye/blob/master/src/teaonly/droideye/TeaServer.java
MainActivity - server initialization https://github.com/Teaonly/android-eye/blob/master/src/teaonly/droideye/MainActivity.java

- 1,763
- 20
- 25
-
This does not work on Android 3 and above, as you cannot do Networking from a UI thread – Paulus Mar 22 '15 at 10:12
-
This does work on Android 3+ and serves images/scripts/etc out of the box. I'd been searching for something that worked for a couple of days. Their implementation handles MIME types without having to specify manually as well. Thank you so much for this info :) – suomi35 Nov 05 '15 at 06:36
Updated WebServer
class (see rendon's reply) that works with current NanoHTTPD version:
private class WebServer extends NanoHTTPD {
public WebServer() {
super(8080);
}
@Override
public Response serve(IHTTPSession session) {
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 newFixedLengthResponse(answer);
}
}

- 2,458
- 1
- 25
- 48
Take a look at how I serve HTML files and other type of files too. I have a AndroidWebServer class which extends the Nanohttpd class. This is my response method -->
public Response serve(IHTTPSession session) {
String uri=session.getUri();
String msg = "<html><body><h1>Hello server</h1>\n";
File [] arrayfile;
int i=0;
try{
session.parseBody(new HashMap<String, String>());
}catch (ResponseException | IOException r){
r.printStackTrace();
}
Map<String, String> parms = session.getParms();
if (parms.get("username") == null) {
msg += "<form action='?' method='get'>\n <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n";
} else {
msg += "<p>Hello, " + parms.get("username") + "!</p>";
}
msg += "<br><br><a href='/Open_rap'>Open Image of Lionel Messi</a><br><br>";
msg += "<br><br><a href='/files'>Browse Files</a><br><br>";
msg += "<br><br><a href='/getmethod'>GET METHOD OPERATION</a><br><br>";
msg += "<br><br><a href='/postmethod'>POST METHOD OPERATION</a><br><br>";
msg += "<br><br><a href='/jquery'>JQUERY OPERATION</a><br><br>";
if(uri.equals("/hello")){
String response="Hello World";
return newFixedLengthResponse(response);
}
else if(uri.equals("/getmethod")){
String html="<html><head><h1>Welcome to the Form</h1><head/><body>";
if(parms.get("name")==null){
html +="<form action='' method='get'> \n " +
"<p>Enter Your Name:</p> <input type='text' name='name'>" +
"</form>" +
"</body>";
}
else{
html +="<p>Hello Mr. "+ parms.get("name") +"</p>"+
"</body> ";
}
html +="</html>";
return newFixedLengthResponse(html);
}
else if(uri.equals("/postmethod")){
String html="<html><head><h1>Welcome to the Form</h1><head/><body>";
Map<String, String> files = new HashMap<String, String>();
Method method = session.getMethod();
String postParameter="";
html +="<form action='' method='post'> \n " +
"<p>Enter Your Name:</p> <input type='text' name='name'>" +
"</form>";
if (Method.POST.equals(method) || Method.PUT.equals(method)) {
try {
session.parseBody(files);
} catch (IOException ioe) {
try {
// return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d("Exception", e.getMessage());
}
} catch (ResponseException re) {
try {
// return newFixedLengthResponse(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d("Exception", re.getMessage());
}
}
}
html +="</body></html>";
String postBody = session.getQueryParameterString();
postParameter = session.getParms().get("name");
Log.d("Postbody",postBody+"\n"+postParameter);
if(postParameter!=null){
String html1="<html><head><h1>"+ postParameter +"</h1><head></html>";
return newFixedLengthResponse(html1);
}
return newFixedLengthResponse(Response.Status.OK,"text/html",html);
}
else if(uri.equals("/Open_rap")){
File root= Environment.getExternalStorageDirectory();
FileInputStream fis = null;
File file = new File(root.getAbsolutePath() + "/www/messi.jpg");
Log.d("Path",root.getAbsolutePath());
try{
if(file.exists())
{
fis = new FileInputStream(file);
}
else
Log.d("FOF :", "File Not exists:");
}catch (FileNotFoundException e)
{
e.printStackTrace();
}
return newFixedLengthResponse(Response.Status.OK,"image/jpeg",fis, file.length() );
}
else {
return newFixedLengthResponse(msg + "</body></html>\n");
}
}
As you can see, I have implemented GET and POST method. You can find each in this part of the code uri.equals("/getmethod")
and uri.equals("/getmethod")
.
Also, you can see the part --> uri.equals("/openrap")
, here I am serving a JPG file to the client's browser and the image is present in the internal directory at /www/
folder.
Ping me if you have any doubts.

- 104
- 1
- 6
Try this... Create 2 packages(activity, util), only for organization In activity create the class MainActivity.java in util create the class AndroidWebServer.java
package awserverfatepi.com.activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import awserverfatepi.com.R;
import awserverfatepi.com.util.AndroidWebServer;
public class MainActivity extends AppCompatActivity {
private static final int DEFAULT_PORT = 8080;
private AndroidWebServer androidWebServer;
private BroadcastReceiver broadcastReceiverNetworkState;
private static boolean isStarted = false;
private CoordinatorLayout coordinatorLayout;
private EditText editTextPort;
private FloatingActionButton floatingActionButtonOnOff;
private View textViewMessage;
private TextView textViewIpAccess;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initGui();
setIpAccess();
floatingActionButtonOnOff = (FloatingActionButton) findViewById(R.id.floatingActionButtonOnOff);
floatingActionButtonOnOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isConnectedInWifi()) {
if (!isStarted && startAndroidWebServer()) {
isStarted = true;
textViewMessage.setVisibility(View.VISIBLE);
floatingActionButtonOnOff.setBackgroundTintList(ContextCompat.getColorStateList(MainActivity.this,
R.color.colorGreen));
editTextPort.setEnabled(false);
} else if (stopAndroidWebServer()) {
isStarted = false;
textViewMessage.setVisibility(View.INVISIBLE);
floatingActionButtonOnOff.setBackgroundTintList(ContextCompat.getColorStateList(MainActivity.this,
R.color.colorRed));
editTextPort.setEnabled(true);
}
} else {
Snackbar.make(coordinatorLayout, getString(R.string.wifi_message), Snackbar.LENGTH_LONG).show();
}
}
});
initBroadcastReceiverNetworkStateChanged();
}
private void initGui() {
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout);
editTextPort = (EditText) findViewById(R.id.editTextPort);
textViewMessage = findViewById(R.id.textViewMessage);
textViewIpAccess = (TextView) findViewById(R.id.textViewIpAccess);
}
private boolean startAndroidWebServer() {
if (!isStarted) {
int port = getPortFromEditText();
try {
if (port == 0) {
throw new Exception();
}
androidWebServer = new AndroidWebServer(port);
androidWebServer.start();
return true;
} catch (Exception e) {
e.printStackTrace();
Snackbar.make(coordinatorLayout, "A porta " + port + " não está funcionando, por favor altere para outra no intervalo" +
" entre 1000 e 9999.", Snackbar.LENGTH_LONG).show();
}
}
return false;
}
private boolean stopAndroidWebServer() {
if (isStarted && androidWebServer != null) {
androidWebServer.stop();
return true;
}
return false;
}
private void setIpAccess() {
textViewIpAccess.setText(getIpAccess());
}
private void initBroadcastReceiverNetworkStateChanged() {
final IntentFilter filters = new IntentFilter();
filters.addAction("android.net.wifi.WIFI_STATE_CHANGED");
filters.addAction("android.net.wifi.STATE_CHANGE");
broadcastReceiverNetworkState = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
setIpAccess();
}
};
super.registerReceiver(broadcastReceiverNetworkState, filters);
}
private String getIpAccess() {
WifiManager wifiManager = (WifiManager) 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));
return "http://" + formatedIpAddress + ":";
}
private int getPortFromEditText() {
String valueEditText = editTextPort.getText().toString();
return (valueEditText.length() > 0) ? Integer.parseInt(valueEditText) : DEFAULT_PORT;
}
public boolean isConnectedInWifi() {
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
NetworkInfo networkInfo = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable() && networkInfo.isConnected()
&& wifiManager.isWifiEnabled() && networkInfo.getTypeName().equals("WIFI")) {
return true;
}
return false;
}
public boolean onKeyDown(int keyCode, KeyEvent evt) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (isStarted) {
new AlertDialog.Builder(this)
.setTitle(R.string.warning)
.setMessage(R.string.dialog_exit_message)
.setPositiveButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
})
.setNegativeButton(getResources().getString(android.R.string.cancel), null)
.show();
} else {
finish();
}
return true;
}
return false;
}
@Override
protected void onDestroy() {
super.onDestroy();
stopAndroidWebServer();
isStarted = false;
if (broadcastReceiverNetworkState != null) {
unregisterReceiver(broadcastReceiverNetworkState);
}
}
}
In the AndroidWebserver.java
package awserverfatepi.com.util;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD;
public class AndroidWebServer extends NanoHTTPD {
public AndroidWebServer(int port) {
super(port);
}
public AndroidWebServer(String hostname, int port) {
super(hostname, port);
}
@Override
public Response serve(IHTTPSession session) {
String msg = "<html><body><h1>Hello World</h1>\n";
Map<String, String> parms = session.getParms();
if (parms.get("username") == null) {
msg += "<form action='?' method='get'>\n <p>Seu nome: <input type='text' name='username'></p>\n" + "</form>\n";
} else {
msg += "<p>Hello, " + parms.get("username") + "!</p>";
}
return newFixedLengthResponse( msg + "</body></html>\n" );
}
}
Don't forget Manifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
And last
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="150dp"
android:scaleType="centerCrop"
android:src="@drawable/header" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorPrimaryLight"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textViewIpAccess"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="http://000.000.000.000:"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editTextPort"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="8080"
android:inputType="numberDecimal"
android:maxLength="4"
android:text="8080"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/textViewMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="50dp"
android:gravity="center"
android:text="@string/message"
android:textColor="@android:color/white"
android:textSize="18sp"
android:visibility="invisible" />
</LinearLayout>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/floatingActionButtonOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:elevation="4dp"
android:src="@drawable/on_btn"
app:backgroundTint="@color/colorRed" />
</android.support.design.widget.CoordinatorLayout>

- 71
- 5
One more example based on previous posts with AssetManager, MimeTypes and routing.
This static server will work for React apps. Your can run React app by using Andorid WebView. Create main/assets/index.html or copy React build folder into main/assets/ directory. Add url http://localhost:8080 to your WebView or browser.
package com.example.httpd;
import androidx.appcompat.app.AppCompatActivity;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import java.io.*;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.Random;
import fi.iki.elonen.NanoHTTPD;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private HttpServer server;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
server = new HttpServer();
try {
server.start();
} catch (IOException ioe) {
Log.d(TAG, "The server could not start.");
}
Log.d(TAG, "Web server initialized.");
}
// DON'T FORGET to stop the server
@Override
public void onDestroy() {
super.onDestroy();
if (server != null)
server.stop();
}
private class HttpServer extends NanoHTTPD {
public static final String
MIME_PLAINTEXT = "text/plain",
MIME_HTML = "text/html",
MIME_JS = "application/javascript",
MIME_CSS = "text/css",
MIME_PNG = "image/png",
MIME_DEFAULT_BINARY = "application/octet-stream",
MIME_XML = "text/xml";
private static final int PORT = 8080;
private static final String TAG = "HttpServer";
private AssetManager assetManager;
public HttpServer() {
super(PORT);
}
@Override
public Response serve(IHTTPSession session) {
assetManager = getAssets();
InputStream inputStream;
Response response = newChunkedResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT, null);
String uri = session.getUri();
try {
if (session.getMethod() == Method.GET && uri != null) {
if (uri.contains(".js")) {
inputStream = assetManager.open(uri.substring(1));
return newChunkedResponse(Response.Status.OK, MIME_JS, inputStream);
} else if (uri.contains(".css")) {
inputStream = assetManager.open(uri.substring(1));
return newChunkedResponse(Response.Status.OK, MIME_CSS, inputStream);
} else if (uri.contains(".png")) {
inputStream = assetManager.open(uri.substring(1));
return newChunkedResponse(Response.Status.OK, MIME_PNG, inputStream);
} else if (uri.contains("/mnt/sdcard")) {
Log.d(TAG, "request for media on sdCard " + uri);
File file = new File(uri);
FileInputStream fileInputStream = new FileInputStream(file);
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String contentType = fileNameMap.getContentTypeFor(uri);
Response streamResponse = newChunkedResponse(Response.Status.OK, contentType, fileInputStream);
Random random = new Random();
String hexString = Integer.toHexString(random.nextInt());
streamResponse.addHeader("ETag", hexString);
streamResponse.addHeader("Connection", "Keep-alive");
return streamResponse;
} else {
inputStream = assetManager.open("index.html");
return newChunkedResponse(Response.Status.OK, MIME_HTML, inputStream);
}
}
} catch (IOException e) {
Log.d(TAG, e.toString());
}
return response;
}
}
}
build.gradle
dependencies {
...
implementation 'org.nanohttpd:nanohttpd:2.3.1'
...
}
res/values/strings.html
<resources>
<string name="http_index">index.html</string>
</resources>
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Have a nice day! :)

- 21
- 4