0.6.0: Ability to handle simultaneous requests; limited React Native QT support

This commit is contained in:
Aleksandr Pantiukhov 2018-11-05 16:16:33 +01:00
parent a120f59261
commit f3381bb5a2
10 changed files with 164 additions and 47 deletions

View File

@ -1,12 +1,13 @@
# react-native-http-bridge
Simple HTTP server for [React Native](https://github.com/facebook/react-native)
Simple HTTP server for [React Native](https://github.com/facebook/react-native).
Created for [Status.im](https://github.com/status-im/status-react).
Since 0.5.0 supports and handles GET, POST, PUT and DELETE requests.
The library can be useful for handling requests with `application/json` content type
(and this is the only content type we support at the current stage) and returning different responses.
Created for [Status.im](https://github.com/status-im/status-react).
Since 0.6.0 can handle millions of requests at the same time and also includes some very basic support for [React Native QT](https://github.com/status-im/react-native-desktop).
## Install

View File

@ -16,8 +16,8 @@ allprojects {
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
minSdkVersion 16

View File

@ -12,6 +12,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.Random;
import android.support.annotation.Nullable;
import android.util.Log;
@ -20,14 +21,13 @@ public class Server extends NanoHTTPD {
private static final String TAG = "HttpServer";
private static final String SERVER_EVENT_ID = "httpServerResponseReceived";
private Map<String, ReadableMap> response;
private ReactContext reactContext;
private Response requestResponse;
private Map<String, Response> responses;
public Server(ReactContext context, int port) {
super(port);
reactContext = context;
response = new HashMap<>();
responses = new HashMap<>();
Log.d(TAG, "Server started");
}
@ -36,11 +36,12 @@ public class Server extends NanoHTTPD {
public Response serve(IHTTPSession session) {
Log.d(TAG, "Request received!");
requestResponse = null;
Random rand = new Random();
String requestId = String.format("%d:%d", System.currentTimeMillis(), rand.nextInt(1000000));
WritableMap request;
try {
request = fillRequestMap(session);
request = fillRequestMap(session, requestId);
} catch (Exception e) {
return newFixedLengthResponse(
Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage()
@ -49,25 +50,28 @@ public class Server extends NanoHTTPD {
this.sendEvent(reactContext, SERVER_EVENT_ID, request);
while (requestResponse == null) {
while (responses.get(requestId) == null) {
try {
Thread.sleep(10);
} catch (Exception e) {
Log.d(TAG, "Exception while waiting: " + e);
}
}
return requestResponse;
Response response = responses.get(requestId);
responses.remove(requestId);
return response;
}
public void respond(int code, String type, String body) {
requestResponse = newFixedLengthResponse(Status.lookup(code), type, body);
public void respond(String requestId, int code, String type, String body) {
requestResponses.put(requestId, newFixedLengthResponse(Status.lookup(code), type, body));
}
private WritableMap fillRequestMap(IHTTPSession session) throws Exception {
private WritableMap fillRequestMap(IHTTPSession session, String requestId) throws Exception {
Method method = session.getMethod();
WritableMap request = Arguments.createMap();
request.putString("url", session.getUri());
request.putString("type", method.name());
request.putString("requestId", requestId);
Map<String, String> files = new HashMap<>();
session.parseBody(files);

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration />
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
desktop/CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES}
\"RCTHttpServer\" PARENT_SCOPE)
set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC}
${CMAKE_CURRENT_SOURCE_DIR}/rcthttpserver.cpp PARENT_SCOPE)

64
desktop/rcthttpserver.cpp Normal file
View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2017-present, Status Research and Development GmbH.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include "rcthttpserver.h"
#include "bridge.h"
#include "eventdispatcher.h"
#include <QDebug>
#include <QJsonDocument>
#include <QByteArray>
#include <QVariantMap>
namespace {
struct RegisterQMLMetaType {
RegisterQMLMetaType() {
qRegisterMetaType<RCTHttpServer*>();
}
} registerMetaType;
} // namespace
class RCTHttpServerPrivate {
public:
Bridge* bridge = nullptr;
};
RCTHttpServer::RCTHttpServer(QObject* parent) : QObject(parent), d_ptr(new RCTHttpServerPrivate) {}
RCTHttpServer::~RCTHttpServer() {}
void RCTHttpServer::setBridge(Bridge* bridge) {
Q_D(RCTHttpServer);
d->bridge = bridge;
}
QString RCTHttpServer::moduleName() {
return "RCTHttpServer";
}
QList<ModuleMethod*> RCTHttpServer::methodsToExport() {
return QList<ModuleMethod*>{};
}
QVariantMap RCTHttpServer::constantsToExport() {
return QVariantMap();
}
void RCTHttpServer::start(int port, QString serviceName) {
}
void RCTHttpServer::stop() {
}
void RCTHttpServer::respond(QString requestId, int code, QString type, QString body) {
}

43
desktop/rcthttpserver.h Normal file
View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2017-present, Status Research and Development GmbH.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#ifndef RCTHTTPSERVER_H
#define RCTHTTPSERVER_H
#include "moduleinterface.h"
#include <QVariantMap>
class RCTHttpServerPrivate;
class RCTHttpServer : public QObject, public ModuleInterface {
Q_OBJECT
Q_INTERFACES(ModuleInterface)
Q_DECLARE_PRIVATE(RCTHttpServer)
public:
Q_INVOKABLE RCTHttpServer(QObject* parent = 0);
~RCTHttpServer();
void setBridge(Bridge* bridge) override;
QString moduleName() override;
QList<ModuleMethod*> methodsToExport() override;
QVariantMap constantsToExport() override;
Q_INVOKABLE void start(int port, QString serviceName);
Q_INVOKABLE void stop();
Q_INVOKABLE void respond(QString requestId, int code, QString type, QString body);
private:
QScopedPointer<RCTHttpServerPrivate> d_ptr;
};
#endif // RCTHTTPSERVER_H

View File

@ -22,7 +22,7 @@ module.exports = {
DeviceEventEmitter.removeListener('httpServerResponseReceived');
},
respond: function (code, type, body) {
Server.respond(code, type, body);
respond: function (requestId, code, type, body) {
Server.respond(requestId, code, type, body);
}
}

View File

@ -7,10 +7,11 @@
#import "WGCDWebServerDataResponse.h"
#import "WGCDWebServerDataRequest.h"
#import "WGCDWebServerPrivate.h"
#include <stdlib.h>
@interface RCTHttpServer : NSObject <RCTBridgeModule> {
WGCDWebServer* _webServer;
WGCDWebServerDataResponse* _requestResponse;
NSMutableDictionary* _requestResponses;
}
@end
@ -25,38 +26,48 @@ RCT_EXPORT_MODULE();
- (void)initResponseReceivedFor:(WGCDWebServer *)server forType:(NSString*)type {
[server addDefaultHandlerForMethod:type requestClass:[WGCDWebServerDataRequest class] processBlock:^WGCDWebServerResponse *(WGCDWebServerRequest* request) {
_requestResponse = NULL;
long long milliseconds = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
int r = arc4random_uniform(1000000);
NSString *requestId = [NSString stringWithFormat:@"%lld:%d", milliseconds, r];
// it's a weird way of doing it, fix it
@try {
if ([WGCDWebServerTruncateHeaderValue(request.contentType) isEqualToString:@"application/json"]) {
WGCDWebServerDataRequest* dataRequest = (WGCDWebServerDataRequest*)request;
[self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived"
body:@{@"postData": dataRequest.jsonObject,
body:@{@"requestId": requestId,
@"postData": dataRequest.jsonObject,
@"type": type,
@"url": request.URL.relativeString}];
} else {
[self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived"
body:@{@"type": type,
body:@{@"requestId": requestId,
@"type": type,
@"url": request.URL.relativeString}];
}
} @catch (NSException *exception) {
[self.bridge.eventDispatcher sendAppEventWithName:@"httpServerResponseReceived"
body:@{@"type": type,
body:@{@"requestId": requestId,
@"type": type,
@"url": request.URL.relativeString}];
}
while (_requestResponse == NULL) {
while ([_requestResponses objectForKey:requestId] == NULL) {
[NSThread sleepForTimeInterval:0.01f];
}
return _requestResponse;
WGCDWebServerDataResponse* response = [_requestResponses objectForKey:requestId];
[_requestResponses removeObjectForKey:requestId];
return response;
}];
}
RCT_EXPORT_METHOD(start:(NSInteger) port
serviceName:(NSString *) serviceName)
{
RCTLogInfo(@"Running HTTP bridge server: %d", port);
RCTLogInfo(@"Running HTTP bridge server: %ld", port);
_requestResponses = [[NSMutableDictionary alloc] init];
dispatch_sync(dispatch_get_main_queue(), ^{
_webServer = [[WGCDWebServer alloc] init];
@ -81,13 +92,16 @@ RCT_EXPORT_METHOD(stop)
}
}
RCT_EXPORT_METHOD(respond:(NSInteger) code
RCT_EXPORT_METHOD(respond: (NSString *) requestId
code: (NSInteger) code
type: (NSString *) type
body: (NSString *) body)
{
NSData* data = [body dataUsingEncoding:NSUTF8StringEncoding];
_requestResponse = [[WGCDWebServerDataResponse alloc] initWithData:data contentType:type];
_requestResponse.statusCode = code;
WGCDWebServerDataResponse* requestResponse = [[WGCDWebServerDataResponse alloc] initWithData:data contentType:type];
requestResponse.statusCode = code;
[_requestResponses setObject:requestResponse forKey:requestId];
}
@end

View File

@ -1,6 +1,7 @@
{
"name": "react-native-http-bridge",
"version": "0.5.2",
"version": "0.6.0",
"description": "A simple HTTP debug server for React Native apps",
"main": "httpServer.js",
"repository": {