A little http(s) server on android to help hybrid apps execute native commands.
Now a days hybrid apps are in huge demand, because of their ease and cost for development. They are good but not all have the same set of functionality. What i mean is each hybrid app development platform have their own tools to help developers write faster codes. But at times they do not provide some basic feature(s). One such feature i came across is android’s famous startActivityForResult()
method. Some of the hybrid apps doesn’t have support for this, specially if its about opening an external app and getting the result from there. IONIC(at the time of writing this article) has a beta version plugin of this feature. So i was given yet another assignment of helping the hybrid apps users.
So i started working on it, i had to figure out a way for hybrid app to get data from a native android app. While i was figuring that out, a client who needed such support asked me “can i make a http request to get the result?”. This gave me an idea, how about i have a intermediate background service that would act like a small web server and accept http request and process them accordingly. The service is native so we can do all processes which a native app can do.
So my next step was to find a way to do that, lucky enough for me there is a nice little library available NanoHTTPD. Its a light weight web server that runs on android. All you need is the NanoHTTPD.java file modify as you wish to.
You can have Activity
or a Service
that starts the server and configure it to accept various requests. In this article i will show you the service way, since my requirement was to have a infinitely running service.
So lets dive in….
First step, as per their documentation, just include the NanoHTTPD.java file into your project. Now in my case i had a BroadcastReceiver
which would listen to an event and accordingly start the intended service and stop the service when that even cease to exists. So my implementation might be a little different from yours.
The below is my code for the web server
/**
* The webservice. This is the core that handles the HTTP requests.
*/
private class WebServer extends NanoHTTPD {
public WebServer() {
super(PORT);
}
/**
* Process the http request here. The available {@link local_server.NanoHTTPD.Method} are
* Method.CUSTOM
* @param session
* The HTTP session
* @return
*/
@Override
public NanoHTTPD.Response serve(IHTTPSession session) {
String data = "<HTML><BODY><H1>No Response !!!</H1></BODY></HTML>";
_uri = session.getUri();
//debugMessage("uri - " + _uri);
Map<String, String> files = new HashMap<String, String>();
Method method = session.getMethod();
if (Method.CUSTOM.equals(method)) {
try {
session.parseBody(files);
} catch (Exception ioe) {
// No post data received
}
}
//Check which request triggered this. If it matches one of the expected one then process it here
if(CUSTOM_URI.equalsIgnoreCase(_uri)) {
if (files.containsKey("MY_DATA")) {
String postData = files.get("MY_DATA");
data= postData;
startActivity(data,false);
}
else {
dataReturned = "<HTML><BODY><H1>Please provide MY_DATA !!!</H1></BODY></HTML>";
}
}
Response response=null;
response = NanoHTTPD.newFixedLengthResponse(Response.Status.OK, MIME_JSON, dataReturned);
response.setChunkedTransfer(false);
response.addHeader("Access-Control-Allow-Origin", "*");
/**
* The OPTIONS request is usually sent on a CORS request, to check if the custom http method
* requested is allowed by the server or not
*/
if(Method.OPTIONS.equals(session.getMethod()))
response.addHeader("Connection", "keep-alive");
response.addHeader("Access-Control-Allow-Methods", Method.GET.name()+","+Method.CUSTOM.name()+","+Method.OPTIONS.name()+","+
Method.CONNECT.name()+","+Method.DELETE.name()+","+Method.HEAD.name()+","+Method.PATCH.name()+","+Method.POST.name()
+","+Method.PUT.name()+","+Method.TRACE.name());
return response;
}
}
I had to create a custom http method i.e. other than post,get,put
etc. so to handle that the browser will first send an OPTIONS
request, to check if the requested method is in the list of allowed http methods. If it is allowed it will, send the request back to the server, with the desired http request header in the
Now that the request has been accepted by the server, next step is to process it and use the data.The main challenge i had was to interrupt/suspend(put it to wait) the server and and process the request and send the response back.
What i found is the request to the server works something like this
But i needed something like this
So in this the web-server thread would accept the request and send it to a different thread, putting the current thread on wait for a max interval possible. Once the operation is done on the 2nd thread, it would call upon the notifyAll()
to resume the thread operation and send back the response to the client.
Since my web- server was running off the service
and i had to do some operation that was possible only inside an activity
or its context
, i used bound service
and Messenger
to communicate the message back to the service.
Lets have a look into the codding side
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_FROM_ACTIVITY_DATA) {
Bundle bundle = msg.getData();
dataReturned=bundle.getString("data");
//Notify the paused server, so that it comes back online
synchronized (webServer) {
webServer.notifyAll();
}
}
}
}
Here synchronized (webServer) { webServer.notifyAll(); }
is basically waking up the suspended web server, the code for suspending the web server is covered in the later part of the article. To make sure no other thread modifies it, i used the synchronized
block. This code is inside my service BackgroundSerice.java
It will be called whenever the client or the activity
publish/sends the data via the Messenger
.So in this once the data is received from the activity
it will resume the web server and sends the response to the client.
To achieve the above, first of all i had to prepare the Handler
to be passed to the Messenger
object that would be passed along the onBind()
of the service to create a two way bound service
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
The below mentioned code is for binding
on the activity
side.
/**
* The data to be sent to the service.
* @param msgData
*/
public void sendMessage(String msgData) {
if (mBound) {
try {
Message message = Message.obtain(null, BackgroundService.MSG_FROM_ACTIVITY_DATA, 1, 1);
message.replyTo = clientMsnger;
Bundle bundle = new Bundle();
bundle.putString("data", msgData);
message.setData(bundle);
mService.send(message); //sending message to service
} catch (RemoteException e) {
e.printStackTrace();
}
}
if (mBound) {
unbindService(mConnection);
mBound = false;
}
finish();
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
@Override
protected void onStart() {
super.onStart();
// Bind to the service so that messages can be transferred between them
bindService(new Intent(this, BackgroundService.class), mConnection,
Context.BIND_AUTO_CREATE);
//Do Your Task
sendMessage("Task Completed");}
Above code is mostly on how to do binding, more about this you can find here.
Next is to show how to suspend the current thread and wait for it.
/**
* Start the intermediate activity to start the Intent
*/
private void startService()
{
Intent sendIntent = new Intent(this,MainActivity.class);
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(sendIntent);
// Condition make the server wait for the response, from starting the intermediate activity
synchronized (webServer){
try {
webServer.wait();
} catch (InterruptedException e) {
Log.e(TAG,e.getMessage());
}
}
}
Again here the synchronized
block has been used to lock the webserver
object and suspend it indefinitely, till the service
receives the message from the activity to resume it. The code for that has already been mentioned above.
So that’s it guys!!
Hope it will be helpful to some developers and hopefully i will share some more experience with you very soon.
Happy codding!!!!!