with akka http, I use getFromDirectory() to serve files from a directory.
How can I say that if a requested file is not found in a dir, look at it to another directory?
Something like this:
lazy val userRoutes: Route =
pathPrefix("files") {
getFromDirectory("./static")
.orElse(getFromDirectory(".generated1")) // this orElse function does not exist. how to achive something like this?
.orElse(getFromDirectory(".generated2")) // this orElse function does }
You can have something like this
val route =
pathPrefix("prefix") {
List("/tmp", "/home/john", "/home/john/Downloads/").map(getFromDirectory).reduceLeft(_ ~ _)
}
Here, it compose the routes for all folders and , since it uses reject when the file is not found, it will fallback to the next route
You can find this usage of reduceLeft in the implementation of getFromBrowseableDirectories
Related
Upon investigating create-react-app's configuration, I found something interesting.
// config/modules.js
...
if (hasTsConfig) {
const ts = require(resolve.sync("typescript", {
basedir: paths.appNodeModules,
}));
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
// Otherwise we'll check if there is jsconfig.json
// for non TS projects.
} else if (hasJsConfig) {
config = require(paths.appJsConfig);
}
...
Unlike reading jsconfig.json file using direct require(paths.appJsConfig), why is here using resolve.sync and ts.readConfigFile to read the tsconfig.json?
...
if (hasTsConfig) {
config = require(paths.appTsConfig)
// Otherwise we'll check if there is jsconfig.json
// for non TS projects.
} else if (hasJsConfig) {
config = require(paths.appJsConfig);
}
...
If I change the code like just above, the result is same. (at least the console output is same.)
There must be a reason why create-react-app using such a complicated way to read the typescript config file.
Why is that?
The ts config reader is a bit smarter than simply reading and parsing a json file. There's two differences I can think of right now:
in tsconfig files, you can use comments. JSON.parse will throw an exception because / is not an allowed character at an arbitrary position
ts config files can extend each other. Simply parsing a JSON file will ignore the extension and you'll receive a config object that doesn't represent what typescript actually uses.
I would love to understand better how Ktor is handling the routing for static content. I have the following hierarchy in my static folder (working directory):
- static
- index.html
- (some files)
- static
- css (directory)
- js (directory)
- (some files)
I'd like to serve all of them. So I was using directly this code in routing:
static {
defaultResource("index.html", "static")
resources("static")
}
Which works very well, but the issue is that it's taking the hand on all requests including my small get:
get("/smoketest"){
call.respondText("smoke test!", ContentType.Text.Plain)
}
What would be the best to handle in general the static content in Ktor?
Here is the code
Thank you
I tried reproducing it locally and made it work with two different approaches.
Put one of these in your static block
file("*", "index.html") // single star will only resolve the first part
file("{...}", "index.html") // tailcard will match anything
Or, put the following get handler as your last route:
val html = File("index.html").readText()
get("{...}") {
call.respondText(html, ContentType.Text.Html)
}
The {...} is a tailcard and matches any request that hasn't been matched yet.
Documentation available here: http://ktor.io/features/routing.html#path
Edit:
For resources I made the following work:
fun Route.staticContent() {
static {
resource("/", "index.html")
resource("*", "index.html")
static("static") {
resources("static")
}
}
}
I can't see your static files in the repository, so here is what it looks like in my project:
How can the following code be re-written to work with the CF Workers feature?
# Start
if(req.url ~ "^/app" ) {
set req.url = regsub(req.url, "^/app/", "/");
set req.http.X-DR-SUBDIR = "app";
}
#end condition
Cloudflare Workers implements the Service Worker standard, so you will need to reimplement the VCL code snippet you posted in terms of a Service Worker.
Before I show you how to do that, consider what happens when a request for https://example.com/apple arrives at the proxy. I would expect the first regex for ^/app to match, but the second one for ^/app/ not to match -- i.e., the request would be passed through with no change to the URL, but with the addition of an X-DR-SUBDIR: app header.
I suspect that behavior is a bug, so I'll first implement a worker as if the first regex were ^/app/.
addEventListener("fetch", event => {
let request = event.request
// Unlike VCL's req.url, request.url is an absolute URL string,
// so we need to parse it to find the start of the path. We'll
// need it as a separate object in order to mutate it, as well.
let url = new URL(request.url)
if (url.pathname.startsWith("/app/")) {
// Rewrite the URL and set the X-DR-SUBDIR header.
url.pathname = url.pathname.slice("/app".length)
// Copying the request with `new Request()` serves two purposes:
// 1. It is the only way to actually change the request's URL.
// 2. It makes `request.headers` mutable. (The headers property
// on the original `event.request` is always immutable, meaning
// our call to `request.headers.set()` below would throw.)
request = new Request(url, request)
request.headers.set("X-DR-SUBDIR", "app")
}
event.respondWith(fetch(request))
})
To revisit the https://example.com/apple case, if we really wanted a Cloudflare Worker which pedantically reproduces the VCL snippet's behavior, we could change these lines (comments elided):
if (url.pathname.startsWith("/app/")) {
url.pathname = url.pathname.slice("/app".length)
// ...
}
to these:
if (url.pathname.startsWith("/app")) {
if (url.pathname.startsWith("/app/")) {
url.pathname = url.pathname.slice("/app".length)
}
// ...
}
I'm creating an image upload API that takes files with POST requests. Here's the code:
def upload = Action(parse.temporaryFile) { request =>
val file = request.body.file
Ok(file.getName + " is uploaded!")
}
The file.getName returns something like: requestBody4386210151720036351asTemporaryFile
The question is how I could get the original filename instead of this temporary name? I checked the headers. There is nothing in it. I guess I could ask the client to pass the filename in the header. But should the original filename be included somewhere in the request?
All the parse.temporaryFile body parser does is store the raw bytes from the body as a local temporary file on the server. This has no semantics in terms of "file upload" as its normally understood. For that, you need to either ensure that all the other info is sent as query params, or (more typically) handle a multipart/form-data request, which is the standard way browsers send files (along with other form data).
For this, you can use the parse.multipartFormData body parser like so, assuming the form was submitted with a file field with name "image":
def upload = Action(parse.multipartFormData) { request =>
request.body.file("image").map { file =>
Ok(s"File uploaded: ${file.filename}")
}.getOrElse {
BadRequest("File is missing")
}
}
Relevant documentation.
It is not sent by default. You will need to send it specifically from the browser. For example, for an input tag, the files property will contain an array of the selected files, files[0].name containing the name of the first (or only) file. (I see there are possibly other properties besides name but they may differ per browser and I haven't played with them.) Use a change event to store the filename somewhere so that your controller can retrieve it. For example I have some jquery coffeescript like
$("#imageFile").change ->
fileName=$("#imageFile").val()
$("#imageName").val(fileName)
The value property also contains a version of the file name, but including the path (which is supposed to be something like "C:\fakepath" for security reasons, unless the site is a "trusted" site afaik.)
(More info and examples abound, W3 Schools, SO: Get Filename with JQuery, SO: Resolve path name and SO: Pass filename for example.)
As an example, this will print the original filename to the console and return it in the view.
def upload = Action(parse.multipartFormData(handleFilePartAsFile)) { implicit request =>
val fileOption = request.body.file("filename").map {
case FilePart(key, filename, contentType, file) =>
print(filename)
filename
}
Ok(s"filename = ${fileOption}")
}
/**
* Type of multipart file handler to be used by body parser
*/
type FilePartHandler[A] = FileInfo => Accumulator[ByteString, FilePart[A]]
/**
* A FilePartHandler which returns a File, rather than Play's TemporaryFile class.
*/
private def handleFilePartAsFile: FilePartHandler[File] = {
case FileInfo(partName, filename, contentType) =>
val attr = PosixFilePermissions.asFileAttribute(util.EnumSet.of(OWNER_READ, OWNER_WRITE))
val path: Path = Files.createTempFile("multipartBody", "tempFile", attr)
val file = path.toFile
val fileSink: Sink[ByteString, Future[IOResult]] = FileIO.toPath(file.toPath())
val accumulator: Accumulator[ByteString, IOResult] = Accumulator(fileSink)
accumulator.map {
case IOResult(count, status) =>
FilePart(partName, filename, contentType, file)
} (play.api.libs.concurrent.Execution.defaultContext)
}
I've got a helper called feature that looks like this:
hbs.registerHelper('feature', function(request, flag, options) {
if (features(flag, request)) {
return options.fn(this);
} else if (options.inverse) {
return options.inverse(this);
}
});
And used in the template over and over like this:
{{feature request "some-feature"}} ... {{/feature}}
I'd love to be able to remove the request part in the template as it's always the same value and never changes. So I imagine I could bind request to feature when it's rendered, and obviously that changes each time and I don't want it spilling out to other request.
Something like:
res.render("page", {
feature: hbs.helper.feature.bind(null, req)
});
Is this possible?
If you are not using known helpers mode then the helper evaluation will check the context so you can pass in a bind like you have above and it should work.
Under the latest code in handlebars master the eval is something like:
helper = helpers.foo || (depth0 && depth0.foo) || helperMissing
helper.call(depth0, 1, {"name":"foo","hash":{},"data":data}
Where depth0 is the current context object. The caveat here is that helpers are given priority so you need to name them differently. You should also be able to do something like {{./foo bar}} to give priority to the local context's version but it appears that we have a bug where that isn't honored under this particular syntax construct.