Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,18 @@ Example code: (read the comments)
* if a relative path is given, it will be relative to cordova assets/www/ in APK.
* "", by default, it will point to cordova assets/www/, it's good to use 'htdocs' for 'www/htdocs'
* if a absolute path is given, it will access file system.
* "/", set the root dir as the www root, it maybe a security issue, but very powerful to browse all dir
* "/", set the root dir as the www root, it maybe a security issue, but very powerful to browse all dir.
* Note the use of custom_paths (which is entirely optionaly) allows you to specify different base
* URLs, and where to serve the content from for that URL. This allows you for example to serve
* content from both the read only app storage area, as well as the read-write data directory.
*/
httpd.startServer({
'www_root' : wwwroot,
'port' : 8080,
'localhost_only' : false
'localhost_only' : false,
'custom_paths' : {
'/rw/' : cordova.file.dataDirectory.substring(7)
}
}, function( url ){
// if server is up, it will return the url of http://<server ip>:port/
// the ip is the active network connection
Expand Down
2 changes: 2 additions & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ xmlns:android="http://schemas.android.com/apk/res/android">
</config-file>

<source-file src="src/ios/CorHttpd.m" />
<header-file src="src/ios/CustomPathHTTPConnection.h" />
<source-file src="src/ios/CustomPathHTTPConnection.m" />

<header-file src="src/ios/CocoaHttpd/HTTPLogging.h" />
<header-file src="src/ios/CocoaHttpd/HTTPServer.h" />
Expand Down
37 changes: 35 additions & 2 deletions src/android/CorHttpd.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
Expand Down Expand Up @@ -42,6 +45,7 @@ public class CorHttpd extends CordovaPlugin {
private static final String OPT_WWW_ROOT = "www_root";
private static final String OPT_PORT = "port";
private static final String OPT_LOCALHOST_ONLY = "localhost_only";
private static final String OPT_CUSTOM_PATHS = "custom_paths";

private String www_root = "";
private int port = 8888;
Expand All @@ -50,6 +54,8 @@ public class CorHttpd extends CordovaPlugin {
private String localPath = "";
private WebServer server = null;
private String url = "";
private Map customPaths = null;
private JSONObject jsonCustomPaths = null;

@Override
public boolean execute(String action, JSONArray inputs, CallbackContext callbackContext) throws JSONException {
Expand Down Expand Up @@ -107,6 +113,7 @@ private PluginResult startServer(JSONArray inputs, CallbackContext callbackConte
www_root = options.optString(OPT_WWW_ROOT);
port = options.optInt(OPT_PORT, 8888);
localhost_only = options.optBoolean(OPT_LOCALHOST_ONLY, false);
jsonCustomPaths = options.optJSONObject(OPT_CUSTOM_PATHS);

if(www_root.startsWith("/")) {
//localPath = Environment.getExternalStorageDirectory().getAbsolutePath();
Expand Down Expand Up @@ -146,11 +153,37 @@ private String __startServer() {
AssetManager am = ctx.getResources().getAssets();
f.setAssetManager( am );

if (jsonCustomPaths != null) {
Iterator keys = jsonCustomPaths.keys();
if (keys != null) {
customPaths = new HashMap(jsonCustomPaths.length());
while (keys.hasNext()) {
String key = (String) keys.next();
String path = jsonCustomPaths.optString(key);
if (!path.startsWith("/") && !path.startsWith("http://") && !path.startsWith("https://")) {
if (path.length() > 0) {
path = "www/" + path;
} else {
path = "www";
}
}
Log.w(LOGTAG, "Custom URL - " + key + " - " + path);
if (path.startsWith("http://") || path.startsWith("https://" )) {
customPaths.put(key, path);
} else {
AndroidFile p = new AndroidFile(path);
p.setAssetManager(am);
customPaths.put(key, p);
}
}
}
}

if(localhost_only) {
InetSocketAddress localAddr = InetSocketAddress.createUnresolved("127.0.0.1", port);
server = new WebServer(localAddr, f);
server = new WebServer(localAddr, f, customPaths);
} else {
server = new WebServer(port, f);
server = new WebServer(port, f, customPaths);
}
} catch (IOException e) {
errmsg = String.format("IO Exception: %s", e.getMessage());
Expand Down
22 changes: 13 additions & 9 deletions src/android/NanoHTTPD.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.InterruptedException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
Expand Down Expand Up @@ -427,7 +428,7 @@ else if (splitbyte==0 || size == 0x7FFFFFFFFFFFFFFFl)

// If the method is POST, there may be parameters
// in data section, too, read it:
if ( method.equalsIgnoreCase( "POST" ))
if ( "POST".equalsIgnoreCase( method ))
{
String contentType = "";
String contentTypeHeader = header.getProperty("content-type");
Expand Down Expand Up @@ -466,7 +467,7 @@ else if (splitbyte==0 || size == 0x7FFFFFFFFFFFFFFFl)
}
}

if ( method.equalsIgnoreCase( "PUT" ))
if ( "PUT".equalsIgnoreCase( method ))
files.put("content", saveTmpFile( fbuf, 0, f.size()));

// Ok, now do the serve()
Expand Down Expand Up @@ -832,15 +833,18 @@ private void sendResponse( String status, String mime, Properties header, InputS

if ( data != null )
{
int pending = data.available(); // This is to support partial sends, see serveFile()
byte[] buff = new byte[theBufferSize];
while (pending>0)
int read;
while ((read = data.read( buff, 0, theBufferSize)) != -1 )
{
int read = data.read( buff, 0, ( (pending>theBufferSize) ? theBufferSize : pending ));
if (read <= 0) break;
out.write( buff, 0, read );
pending -= read;
}
if (read == 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {}
} else {
out.write(buff, 0, read);
}
}
}
out.flush();
out.close();
Expand Down
157 changes: 155 additions & 2 deletions src/android/WebServer.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,168 @@
package com.rjfun.cordova.httpd;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.lang.Override;
import java.lang.String;
import java.net.MalformedURLException;
import java.net.InetSocketAddress;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;


import android.util.Log;

public class WebServer extends NanoHTTPD
{
public WebServer(InetSocketAddress localAddr, AndroidFile wwwroot) throws IOException {
private Map customPaths = null;
private String[] customURIs = new String[0];
private final String LOGTAG = "NanoHTTPD-Cordova";

public WebServer(InetSocketAddress localAddr, AndroidFile wwwroot, Map customPaths ) throws IOException {
super(localAddr, wwwroot);
addCustomPaths(customPaths);
}

public WebServer(int port, AndroidFile wwwroot ) throws IOException {
public WebServer(int port, AndroidFile wwwroot, Map customPaths ) throws IOException {
super(port, wwwroot);
addCustomPaths(customPaths);
}

private void addCustomPaths(Map customPaths) {
this.customPaths = customPaths;
customURIs = new String[customPaths.keySet().size()];
int i = 0;
Iterator keys = customPaths.keySet().iterator();
while (keys.hasNext()) {
String path = (String) keys.next();
customURIs[i] = path;
i++;
}
Arrays.sort(customURIs, new Comparator<String>() {
@Override
public int compare(String s, String t1) {
return t1.length() - s.length();
}
});
for (i = 0; i < customURIs.length; i++) {
Log.i( LOGTAG, "Custom Path: " + customURIs[i]);
}
}

public Response serve( String uri, String method, Properties header, Properties parms, Properties files )
{
if (uri == null || method == null) {
return null;
}
Log.i( LOGTAG, method + " '" + uri + "' " );
for (int i = 0; i < customURIs.length; i++) {
String testURI = customURIs[i];
if (uri.startsWith(testURI)) {
Log.i( LOGTAG, method + " '" + uri + "' " );
String newURI = uri.substring(testURI.length());
Object customPath = customPaths.get(testURI);
if (customPath instanceof String) {
URL url = null;
HttpURLConnection connection = null;
InputStream in = null;

// Open the HTTP connection
try {
url = new URL(((String) customPath) + newURI);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
}
try {
in = new InputStreamWithOverloadedClose(connection.getInputStream(), connection);
} catch (IOException e) {
e.printStackTrace();
}
String datatype = connection.getContentType(); //NanoHTTPD.MIME_DEFAULT_BINARY
Response response = new NanoHTTPD.Response(NanoHTTPD.HTTP_OK, datatype, in);
if (connection.getContentEncoding() != null)
response.addHeader("Content-Encoding", connection.getContentEncoding());
if (connection.getContentLength() != -1)
response.addHeader("Content-Length", "" + connection.getContentLength());
if (connection.getHeaderField("Date") != null)
response.addHeader("Date", connection.getHeaderField("Date"));
if (connection.getHeaderField("Last-Modified") != null)
response.addHeader("Last-Modified", connection.getHeaderField("Last-Modified"));
if (connection.getHeaderField("Cache-Control") != null)
response.addHeader("Cache-Control", connection.getHeaderField("Cache-Control"));
return response;
} else {
return serveFile( newURI, header, (AndroidFile) customPath, true );
}
}
}
return super.serve( uri, method, header, parms, files );
}

public class InputStreamWithOverloadedClose extends InputStream {
protected InputStream is;
protected HttpURLConnection connection;

public InputStreamWithOverloadedClose(InputStream is, HttpURLConnection connection) {
super();
this.is = is;
this.connection = connection;
}

@Override
public int available() throws IOException {
return is.available();
}

@Override
public void close() throws IOException {
is.close();
connection.disconnect();
}

@Override
public void mark(int readlimit) {
is.mark(readlimit);
}

@Override
public boolean markSupported() {
return is.markSupported();
}

@Override
public int read() throws IOException {
return is.read();
}

@Override
public int read(byte[] buffer) throws IOException {
return is.read(buffer);
}

@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
return is.read(buffer, offset, length);
}

@Override
public synchronized void reset() throws IOException {
is.reset();
}

@Override
public long skip(long byteCount) throws IOException {
return is.skip(byteCount);
}
}
}
28 changes: 28 additions & 0 deletions src/ios/CorHttpd.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import "DDLog.h"
#import "DDTTYLogger.h"
#import "HTTPServer.h"
#import "CustomPathHTTPConnection.h"

@interface CorHttpd : CDVPlugin {
// Member variables go here.
Expand All @@ -14,6 +15,7 @@ @interface CorHttpd : CDVPlugin {
@property(nonatomic, retain) HTTPServer *httpServer;
@property(nonatomic, retain) NSString *localPath;
@property(nonatomic, retain) NSString *url;
@property(nonatomic, retain) NSMutableDictionary *customPaths;

@property (nonatomic, retain) NSString* www_root;
@property (assign) int port;
Expand Down Expand Up @@ -43,6 +45,7 @@ @implementation CorHttpd
#define OPT_WWW_ROOT @"www_root"
#define OPT_PORT @"port"
#define OPT_LOCALHOST_ONLY @"localhost_only"
#define OPT_CUSTOM_PATHS @"custom_paths"

#define IP_LOCALHOST @"127.0.0.1"
#define IP_ANY @"0.0.0.0"
Expand Down Expand Up @@ -142,6 +145,31 @@ - (void)startServer:(CDVInvokedUrlCommand*)command
[DDLog addLogger:[DDTTYLogger sharedInstance]];
self.httpServer = [[HTTPServer alloc] init];

[self.httpServer setConnectionClass:[CustomPathHTTPConnection class]];

NSDictionary* customPathsFromOptions = (NSDictionary *)[options valueForKey:OPT_CUSTOM_PATHS];
if (customPathsFromOptions == nil) {
self.customPaths = [NSMutableDictionary dictionaryWithCapacity:0];
} else {
self.customPaths = [NSMutableDictionary dictionaryWithCapacity:[customPathsFromOptions count]];
[customPathsFromOptions enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
NSString* customPath = (NSString*) key;
NSString* path = (NSString*) obj;
NSString* localPath = nil;
const char * docroot = [path UTF8String];
if(*docroot == '/' || [path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) {
localPath = path;
} else {
NSString* basePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"www"];
localPath = [NSString stringWithFormat:@"%@/%@", basePath, path];
}
NSLog(@"Custom Path: %@ - %@", customPath, localPath);
[self.customPaths setObject:localPath forKey:customPath];
}];
}
[CustomPathHTTPConnection setCustomPaths:self.customPaths];

// Tell the server to broadcast its presence via Bonjour.
// This allows browsers such as Safari to automatically discover our service.
//[self.httpServer setType:@"_http._tcp."];
Expand Down
Loading