Why is Akka-Http Routing going wrong here? - scala

So basically, I'm writing some code that will make it possible for users to upload a file to a server. I've already succeeded in uploading a file via an HTML form (with MultiPart.FormData), but when I try 'curl -X POST -F file="filepath" localhost:8080/upload', I get a '404 not found' message.
I already read the documentation about Akka, but I just can't get to know why it works in one way and not in the other. What am I doing wrong here?
val route =
post {
path("upload") {
fileUpload("file") {
case (metadata, byteSource) =>
val sink = FileIO.toPath(Paths.get("path of the image") resolve metadata.fileName)
val writeResult = byteSource.runWith(sink)
onSuccess(writeResult) { _ =>
complete("file got uploaded")
}
}
}
} ~
complete("404 not found")

You can see in path directive source that it accepts a path prefix with the path end. So if you use path("upload") it will accept only paths ended with /upload/ but won't accept paths ended with /upload (without path end symbol /).
If you wont to use both /upload/ and /upload paths, you should use
pathPrefix("upload") ~ pathEndOrSingleSlash
Also, you can use ignoreTrailingSlash directive.

Related

Akka HTTP server receives file with other fields

I have created a small Akka HTTP server to receive an uploaded file.
path("upload"){
uploadedFile("csv"){
case (metadata, file) =>{
println("file received " + file.length() );
complete("hahahah")
}
}
}
I can receive the file successfully but I cannot access other fields in this POST request. The field "csv" contains the file to be uploaded, while another field, "name", contains the user-defined name. I cannot access the data in "name". Can anybody give me some clues about how to get it?
You can use fromFields('user) to get user name. But unfortunately you will get this exception: java.lang.IllegalStateException: Substream Source cannot be materialized more than once It's known issue: https://github.com/akka/akka-http/issues/90
As workaround, you can use toStrictEntity directive:
toStrictEntity(3.seconds) {
formFields('user) { (user) =>
uploadedFile("csv") {
case (metadata, file) => {
println(s"file received by $user" + file.length())
complete("hahahah")
}
}
}
}
}
I don't think it's a good idea because you will read the entire request entity into memory and it works if you have the small entity.
As a better solution, you can implement your own uploadedFile directive that will extract needed parts and fields from your multipart form data, see uploadedFile source code as example: https://github.com/akka/akka-http/blob/v10.0.10/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala

How do I access the full path of a request in Akka HTTP request?

In some contexts I can match on the remaining path using a PathDirective to get the information I need about the path. For example, when route below is directly bound and handled by Akka HTTP, every request will echo back the requested path as desired.
val route =
path(Remaining) { path =>
complete(path)
}
However, when the above route is combined elsewhere in the application, the path variable above may only hold part of the requested path not giving the desired results.
For example if the actual bound route is be,
val actualRoute = pathPrefix("echo") { route }
The "echo/" part of the overall path will be missing from the response given to the user.
How can the complete path be reliably accessed?
Directives extractMatchedPath and extractUnmatchedPath let you access the path without matching the path like you path directive does above. These two can be combined to construct the full path:
val route =
extractMatchedPath { matched =>
extractUnmatchedPath { unmatched =>
complete((matched.dropChars(1) ++ unmatched).toString)
}
}
However it is probably cleaner just to extract the Path from the URI directly:
val route =
extractUri { uri =>
complete(uri.toRelative.path.dropChars(1).toString)
}
Note that in both cases we needed to call .dropChars(1) to drop the initial forward slash and be consistent with the output you got using the path directive.

Require header across routes without giving up 404

I’d like to require a header in my akka-http routes and can do so via
val route = headerValueByName("foo") { foo =>
pathPrefix("path") {
get {
...
} ~ ...
}
}
However, now any requests that don't match a path will get rejected with 400 (missing header) and not 404.
Is there a neat way to get around this without repeatedly moving headerValueByName after the path matchers?
That is, is there a way to only apply an outer directive ( headerValueByName) and its rejections if the inner path and method matchers are successful?
You don't specify what you want to do in case the header is not specified, so I'll asume you want to return 400 (Bad request).
A possible solution is to use the optionalHeaderValueByName directive and then complete the request with the specified error, for example:
val route = optionalHeaderValueByName("foo") { optionalHeader =>
optionalHeader map { header =>
// rest of your routes
} getOrElse complete(StatusCodes.BadRequest)
}

How can I redirect all unknown URLs in lift framework?

I'm not very familiar with lift framework and wanted to know if the following use case is possible using lift framework.
On server1, Lift is serving REST webservice at following url "/contact/"
However, if the client sends request to the following URL https://server1/contact/meet/" then it is not implemented on this specific server but "might" be implemented by another server. Can Lift redirect any such unsupported URLs to some specific server? Eg, in 302 response, can Location be specified by Lift to https://server2/contact/meet/ ?
Please note that these are unknown URLs and can't be configured statically.
Yeah, I get it. Maybe you need LiftRules.dispatch and net.liftweb.http.DoRedirectResponse. Following is the code I try to solve your trouble.
// The code should in the server1; JsonDSL will be used by JsonResponse
class Boot extends Bootable with JsonDSL {
def boot {
initDispatch
}
def initDispatch {
LiftRules.dispatch.append {
case Req("contact" :: url :: Nil, _, GetRequest) => {
() => Full(
if (url == "join") {
// or other url that match what will be implemented in server1
// your implementation, say JsonResponse
JsonResponse("server1" -> true)
} else {
// if the url part does not match simply redirect to server2,
// then you have to deal with how to process the url in server2
DoRedirectResponse("https://server2/contact/meet/")
}
)
}
}
}
}
Anyway, hope it helps.

Spray testing basicauth from js html

I have code like:
https://gist.github.com/daaatz/7665224
but dont know how to test request.
Trying mydomain/secured?user=John&password=p4ssw0rd etc but nothing works.
Can some one tell me or show example in js+html how to check is it working fine ?
Thanks
I've never used BasicAuth in Spray, so i'm not sure if this would be the complete answer, but i hope this will help you.
At first. in spray there is a great spray-testkit written on top of akka testkit. You should definitely check out SecurityDirectives test on github, this will show you how to test basic authentication. A little example, to make this simpler:
As for your route example, i would better edit to the following one:
val myRoute =
(path("secured") & get) {
authenticate(BasicAuth(myUserPassAuthenticator _, realm = "secure site")) {
userName => complete(s"The user is '$userName'")
}
}
}
Adding get directive will specify that this route expects a Get request and sealRoute is obsolete cause RejectionHandler and ExceptionHandler are provided implicitly with runRoute. It is used only in tests, if you want wo check exceptions/rejections.
Now in your tests you should construct auth entities, similar to the test one:
val challenge = `WWW-Authenticate`(HttpChallenge("Basic", "Realm"))
val doAuth = BasicAuth(UserPassAuthenticator[BasicUserContext] { userPassOption ⇒
Future.successful(Some(BasicUserContext(userPassOption.get.user)))
}, "Realm")
And you test case:
"simple auth test" in {
Get("security") ~> Authorization(BasicHttpCredentials("Alice", "")) ~> {
authenticate(doAuth) { echoComplete }
} ~> check { responseAs[String] === "BasicUserContext(Alice)" }
}
In Get("security") specifies that your test will send a Get request on "/security", then add Authorization to the test request, some action and the check part to test the request.
I've never tried to test BasicAuth, so there could be some mistakes.
I would look into CURL for testing routes in web applications, but I've also used the chrome extension Postman with great results as well.