Using NanoHTTPD in Android
Asked Answered
V

6

39

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.

Voiced answered 13/1, 2013 at 23:3 Comment(2)
Unless 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.Syndactyl
well, 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.Voiced
C
49

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:

Cremona answered 8/9, 2013 at 15:23 Comment(14)
Thanks for posting this, I should have added my own code once I figured it out (which took a while).Voiced
Adding 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 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!Behalf
You are welcome. I used a server for a college project(bitbucket.org/rendon/smsserver) and NanoHTTPD did the work so well.Cremona
@AndrewCottrell "and viewed the webpage from my PC. Works" How do you communicate between webpage and android activity?Kalinda
Thanks, works on Lollipop. I only had to change R.layout.main to R.layout.activity_mainBaudelaire
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.Teresita
@Teresita 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 :(.Cremona
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.Frans
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.Hus
@MartinKonecny I shared this for didactic purposes, it's basically a Hello World, not intended to run in production :).Cremona
Can I run this server as a part of an instrumented test?Crosley
@Crosley Unfortunately, I couldn't tell.Cremona
@AndrewCottrell where did you put the nanohttpd.java file?Levo
@Frans would you happen to have an example of it in a service?Levo
B
10

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

Backstretch answered 20/3, 2013 at 12:45 Comment(2)
This does not work on Android 3 and above, as you cannot do Networking from a UI threadTidemark
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 :)Hollister
E
9

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);
    }

}
Erdah answered 18/11, 2015 at 2:45 Comment(0)
M
7

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.

Musser answered 31/5, 2018 at 8:31 Comment(0)
I
3

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>
Inwards answered 4/7, 2016 at 2:35 Comment(0)
B
0

This static server serves HTML file, does routing and works for React apps as well. You 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" />
Biliary answered 14/9, 2022 at 12:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.