Skip to content

Commit

Permalink
Add ability to dynamically add/remove static paths
Browse files Browse the repository at this point in the history
  • Loading branch information
wch committed Oct 18, 2018
1 parent ff27d6c commit bfcd28c
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 73 deletions.
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Generated by roxygen2: do not edit by hand

export(addStaticPaths)
export(decodeURI)
export(decodeURIComponent)
export(encodeURI)
export(encodeURIComponent)
export(getRNGState)
export(getStaticPaths)
export(interrupt)
export(ipFamily)
export(rawToBase64)
export(removeStaticPaths)
export(runServer)
export(service)
export(startDaemonizedServer)
Expand Down
13 changes: 13 additions & 0 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ stopAllServers <- function() {
invisible(.Call('_httpuv_stopAllServers', PACKAGE = 'httpuv'))
}

#' @export
getStaticPaths <- function(handle) {
.Call('_httpuv_getStaticPaths', PACKAGE = 'httpuv', handle)
}

addStaticPaths_ <- function(handle, paths) {
.Call('_httpuv_addStaticPaths_', PACKAGE = 'httpuv', handle, paths)
}

removeStaticPaths_ <- function(handle, paths) {
.Call('_httpuv_removeStaticPaths_', PACKAGE = 'httpuv', handle, paths)
}

base64encode <- function(x) {
.Call('_httpuv_base64encode', PACKAGE = 'httpuv', x)
}
Expand Down
59 changes: 16 additions & 43 deletions R/httpuv.R
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ AppWrapper <- setRefClass(
# If .app is a reference class, accessing .app$staticPaths can error if
# not present.
if (class(try(.app$staticPaths, silent = TRUE)) == "try-error") {
.staticPaths <<- .normalizeStaticPaths(NULL)
.staticPaths <<- normalizeStaticPaths(NULL)
} else {
.staticPaths <<- .normalizeStaticPaths(.app$staticPaths)
.staticPaths <<- normalizeStaticPaths(.app$staticPaths)
}
},
onHeaders = function(req) {
Expand Down Expand Up @@ -268,48 +268,7 @@ AppWrapper <- setRefClass(
},
getStaticPaths = function() {
.staticPaths
},
.normalizeStaticPaths = function(paths) {
# This function always returns a named character vector.

if (is.null(paths) || length(paths) == 0) {
return(empty_named_vec())
}

if (any_unnamed(paths)) {
stop("staticPaths must be a named character vector, a named list of strings, or NULL.")
}

# If list, convert to vector.
if (is.list(paths)) {
paths <- unlist(paths, recursive = FALSE)
}

if (!is.character(paths)) {
stop("staticPaths must be a named character vector, a named list of strings, or NULL.")
}

# If we got here, it is a named character vector.

# Make sure paths have a leading '/'. Save in a separate var because
# we later call normalizePath(), which drops names.
path_names <- vapply(names(paths), function(path) {
if (path == "") {
stop("All paths must be non-empty strings.")
}
# Ensure there's a leading / for every path
if (substr(path, 1, 1) != "/") {
path <- paste0("/", path)
}
path
}, "")

paths <- normalizePath(paths, mustWork = TRUE)
names(paths) <- path_names

paths
}

)
)

Expand Down Expand Up @@ -698,6 +657,20 @@ startDaemonizedServer <- startServer
stopDaemonizedServer <- stopServer



#' @export
addStaticPaths <- function(handle, paths) {
paths <- normalizeStaticPaths(paths)
invisible(addStaticPaths_(handle, paths))
}

#' @export
removeStaticPaths <- function(handle, paths) {
paths <- as.character(paths)
invisible(removeStaticPaths_(handle, paths))
}


# Needed so that Rcpp registers the 'httpuv_decodeURIComponent' symbol
legacy_dummy <- function(value){
.Call('httpuv_decodeURIComponent', PACKAGE = "httpuv", value)
Expand Down
42 changes: 42 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,45 @@ any_unnamed <- function(x) {
# List with name attribute; check for any ""
any(!nzchar(nms))
}


# This function always returns a named character vector of path mappings. The
# names will all start with "/".
normalizeStaticPaths <- function(paths) {
if (is.null(paths) || length(paths) == 0) {
return(empty_named_vec())
}

if (any_unnamed(paths)) {
stop("paths must be a named character vector, a named list of strings, or NULL.")
}

# If list, convert to vector.
if (is.list(paths)) {
paths <- unlist(paths, recursive = FALSE)
}

if (!is.character(paths)) {
stop("paths must be a named character vector, a named list of strings, or NULL.")
}

# If we got here, it is a named character vector.

# Make sure paths have a leading '/'. Save in a separate var because
# we later call normalizePath(), which drops names.
path_names <- vapply(names(paths), function(path) {
if (path == "") {
stop("All paths must be non-empty strings.")
}
# Ensure there's a leading / for every path
if (substr(path, 1, 1) != "/") {
path <- paste0("/", path)
}
path
}, "")

paths <- normalizePath(paths, mustWork = TRUE)
names(paths) <- path_names

paths
}
38 changes: 38 additions & 0 deletions src/RcppExports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,41 @@ BEGIN_RCPP
return R_NilValue;
END_RCPP
}
// getStaticPaths
Rcpp::CharacterVector getStaticPaths(std::string handle);
RcppExport SEXP _httpuv_getStaticPaths(SEXP handleSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< std::string >::type handle(handleSEXP);
rcpp_result_gen = Rcpp::wrap(getStaticPaths(handle));
return rcpp_result_gen;
END_RCPP
}
// addStaticPaths_
Rcpp::CharacterVector addStaticPaths_(std::string handle, Rcpp::CharacterVector paths);
RcppExport SEXP _httpuv_addStaticPaths_(SEXP handleSEXP, SEXP pathsSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< std::string >::type handle(handleSEXP);
Rcpp::traits::input_parameter< Rcpp::CharacterVector >::type paths(pathsSEXP);
rcpp_result_gen = Rcpp::wrap(addStaticPaths_(handle, paths));
return rcpp_result_gen;
END_RCPP
}
// removeStaticPaths_
Rcpp::CharacterVector removeStaticPaths_(std::string handle, Rcpp::CharacterVector paths);
RcppExport SEXP _httpuv_removeStaticPaths_(SEXP handleSEXP, SEXP pathsSEXP) {
BEGIN_RCPP
Rcpp::RObject rcpp_result_gen;
Rcpp::RNGScope rcpp_rngScope_gen;
Rcpp::traits::input_parameter< std::string >::type handle(handleSEXP);
Rcpp::traits::input_parameter< Rcpp::CharacterVector >::type paths(pathsSEXP);
rcpp_result_gen = Rcpp::wrap(removeStaticPaths_(handle, paths));
return rcpp_result_gen;
END_RCPP
}
// base64encode
std::string base64encode(const Rcpp::RawVector& x);
RcppExport SEXP _httpuv_base64encode(SEXP xSEXP) {
Expand Down Expand Up @@ -193,6 +228,9 @@ static const R_CallMethodDef CallEntries[] = {
{"_httpuv_makePipeServer", (DL_FUNC) &_httpuv_makePipeServer, 9},
{"_httpuv_stopServer", (DL_FUNC) &_httpuv_stopServer, 1},
{"_httpuv_stopAllServers", (DL_FUNC) &_httpuv_stopAllServers, 0},
{"_httpuv_getStaticPaths", (DL_FUNC) &_httpuv_getStaticPaths, 1},
{"_httpuv_addStaticPaths_", (DL_FUNC) &_httpuv_addStaticPaths_, 2},
{"_httpuv_removeStaticPaths_", (DL_FUNC) &_httpuv_removeStaticPaths_, 2},
{"_httpuv_base64encode", (DL_FUNC) &_httpuv_base64encode, 1},
{"_httpuv_encodeURI", (DL_FUNC) &_httpuv_encodeURI, 1},
{"_httpuv_encodeURIComponent", (DL_FUNC) &_httpuv_encodeURIComponent, 1},
Expand Down
45 changes: 45 additions & 0 deletions src/httpuv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "thread.h"
#include "httpuv.h"
#include "auto_deleter.h"
#include "socket.h"
#include <Rinternals.h>


Expand Down Expand Up @@ -232,6 +233,10 @@ void closeWS(SEXP conn,
}


// ============================================================================
// Create/stop servers
// ============================================================================

// [[Rcpp::export]]
Rcpp::RObject makeTcpServer(const std::string& host, int port,
Rcpp::Function onHeaders,
Expand Down Expand Up @@ -412,6 +417,46 @@ void stop_loop_timer_cb(uv_timer_t* handle) {
}


// ============================================================================
// Static file serving
// ============================================================================

boost::shared_ptr<WebApplication> get_pWebApplication(uv_stream_t* pServer) {
// Copy the Socket shared_ptr
boost::shared_ptr<Socket> pSocket(*(boost::shared_ptr<Socket>*)pServer->data);
return pSocket->pWebApplication;
}

boost::shared_ptr<WebApplication> get_pWebApplication(std::string handle) {
uv_stream_t* pServer = internalize_str<uv_stream_t>(handle);
return get_pWebApplication(pServer);
}

//' @export
// [[Rcpp::export]]
Rcpp::CharacterVector getStaticPaths(std::string handle) {
ASSERT_MAIN_THREAD()
std::map<std::string, std::string> paths = get_pWebApplication(handle)->getStaticPaths();
return toCharacterVector(paths);
}

// [[Rcpp::export]]
Rcpp::CharacterVector addStaticPaths_(std::string handle, Rcpp::CharacterVector paths) {
ASSERT_MAIN_THREAD()
std::map<std::string, std::string> new_paths = toStringMap(paths);
std::map<std::string, std::string> all_paths = get_pWebApplication(handle)->addStaticPaths(new_paths);
return toCharacterVector(all_paths);
}

// [[Rcpp::export]]
Rcpp::CharacterVector removeStaticPaths_(std::string handle, Rcpp::CharacterVector paths) {
ASSERT_MAIN_THREAD()
std::vector<std::string> rm_paths = Rcpp::as<std::vector<std::string>>(paths);
std::map<std::string, std::string> all_paths = get_pWebApplication(handle)->removeStaticPaths(rm_paths);
return toCharacterVector(all_paths);
}


// ============================================================================
// Miscellaneous utility functions
// ============================================================================
Expand Down
49 changes: 49 additions & 0 deletions src/utils.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
#include "utils.h"
#include <iostream>
#include <Rcpp.h>

void trace(const std::string& msg) {
#ifdef DEBUG_TRACE
std::cerr << msg << std::endl;
#endif
}


std::map<std::string, std::string> toStringMap(Rcpp::CharacterVector x) {
ASSERT_MAIN_THREAD()

std::map<std::string, std::string> strmap;

if (x.size() == 0) {
return strmap;
}

Rcpp::CharacterVector names = x.names();
if (names.isNULL()) {
throw Rcpp::exception("Error converting CharacterVector to map<string, string>: vector does not have names.");
}


for (int i=0; i<x.size(); i++) {
std::string name = Rcpp::as<std::string>(names[i]);
std::string value = Rcpp::as<std::string>(x[i]);
if (name == "") {
throw Rcpp::exception("Error converting CharacterVector to map<string, string>: element has empty name.");
}

strmap.insert(
std::pair<std::string, std::string>(name, value)
);
}

return strmap;
}


Rcpp::CharacterVector toCharacterVector(const std::map<std::string, std::string>& strmap) {
ASSERT_MAIN_THREAD()

Rcpp::CharacterVector values(strmap.size());
Rcpp::CharacterVector names(strmap.size());

std::map<std::string, std::string>::const_iterator it = strmap.begin();
for (int i=0; it != strmap.end(); i++, it++) {
values[i] = it->second;
names[i] = it->first;
}

values.attr("names") = names;
return values;
}
5 changes: 5 additions & 0 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdarg.h>
#include <string>
#include <vector>
#include <Rcpp.h>
#include "thread.h"

// A callback for deleting objects on the main thread using later(). This is
Expand Down Expand Up @@ -78,4 +79,8 @@ inline std::string to_lower(const std::string& str) {
return lowered;
}

// Convert between map<string, string> and CharacterVector
std::map<std::string, std::string> toStringMap(Rcpp::CharacterVector x);
Rcpp::CharacterVector toCharacterVector(const std::map<std::string, std::string>& strmap);

#endif
Loading

0 comments on commit bfcd28c

Please sign in to comment.