How to redirect all 404 errors - swift

How would I catch all 404 errors and redirect to /app/index.html (I'm trying to satisfy Angular's need to forward all not found resources to index.html) ? I thought implementing my own middleware would work, but not sure I'm doing it right
public final class ForwardToAngularMiddleware: Middleware {
public func respond(to req: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
do {
let response: Future<Response> = try next.respond(to: req)
return response
} catch let error as AbortError {
if error.status == .notFound && req.http.url.path.starts(with: "/app") {
return Future.map(on: req) { req.redirect(to: "/index.html") }
}
throw error
}
}
}
My program never hits the catch block no matter what URL I send. I am configuring my middleware like this:
middlewares.use(FileMiddleware.self)
middlewares.use(ForwardToAngularMiddleware())
middlewares.use(ErrorMiddleware.self)
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)

You may be hitting a couple of issues here. First, the abort error could be being thrown in a future, in which case you need to add a catchMap to the next.respond(to:) call to catch that case.
It also may not throw (though this is unlikely), so you can try unwrapping the response and checking the status code.
Have you put a breakpoint in to see if it ever hits it etc?

Related

Chained Throwing Futures in SwiftNIO & Vapor

In Vapor 4, I'm processing a post request by calling a request on a 3rd party API and returning a value based on the result I get back. The following code results in the error: "Invalid conversion from throwing function ... to non-throwing function"
app.post("activate") { req -> EventLoopFuture<ActivationRequestResponse> in
return req.client.post("https://api.example.com/activation", headers: HTTPHeaders(), beforeSend: { (req) in
try req.content.encode(RequestBody(value: someValue), as: .json)
})
.map { (response) -> ActivationRequestResponse in
let response = try response.content.decode(ResponseModel.self)
return ActivationRequestResponse(success: true, message: "success")
}
}
I can't seem to use try in my chained map() after getting the API result. The above code will work if I add a ! to the try in let response = try response.content.decode(ResponseModel.self) inside the map, but ideally I want to catch this error. The first try used when creating the response body seems to be implicitly passed back up the chain, but not the second.
What am I doing wrong? How do I catch the error when decoding the response content? Why is the first try caught but not the second?
The property of map is that it will just transform a value on the “success path”. Your transformation may however fail which means that you presumably want the future to fail too.
Whenever you want to transform a value with a function that either succeeds or fails you need to use one of the flatMap* functions.
In your case, try replacing map with flatMapThrowing and then it should work.
To expand on Johannes Weiss' answer, to have a throwing closure that returns a future, you need something like:
future.flatMap {
do {
return try liveDangerously()
} catch {
future.eventLoop.makeFailedFuture(error)
}
}
After doing this too many times, I decided to roll my own (though the name is a bit dubious):
extension EventLoopFuture {
#inlinable
public func flatterMapThrowing<NewValue>(file: StaticString = #file,
line: UInt = #line,
_ callback: #escaping (Value) throws -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
return self.flatMap(file: file, line: line) { (value: Value) -> EventLoopFuture<NewValue> in
do {
return try callback(value)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
}
}
}
That way you can just write:
future.flatterMapThrowing {
return try liveDangerously()
}

Unable to catch exception being thrown from another function

I am expecting the "throw RuntimeException" in ServerHandler to proceed to the catch block in registerAccount when error code 403 pops out from the server, but I am unable to catch the error... below is my code:
LoginRepo.kt:
private fun registerAccount(context: Context, jsonObject: JSONObject, username: String, password: String): Result<LoggedInUser> {
try {
ServerHandler.getInstance(context).makeHttpRequest(
"http://www.mywebpage.com/index.php",
Request.Method.POST,
jsonObject
)
return Result.Success(LoggedInUser(java.util.UUID.randomUUID().toString(), username))
} catch (e: Throwable) {
return Result.Error(IOException("Error registering account: ", e))
}
}
ServerHandler.kt:
#Throws(RuntimeException::class)
fun makeHttpRequest(url: String, method: Int, jsonBody: JSONObject? = null):Any {
// Instantiate the RequestQueue.
Log.d("makeHttpRequest","Sending request!!!!")
var stringRequest = when (method) {
Request.Method.POST ->
object : StringRequest(method, url,
Response.Listener<String> { response ->
Log.d("requestPOST", response)
}, Response.ErrorListener { error ->
#Throws(RuntimeException::class)
when(error.networkResponse.statusCode) {
403 -> {
throw RuntimeException("Username is taken.") //<--RuntimeException
}
else-> {
Log.d("UNHANDLED ERROR:", error.toString())
}
}})
}
}
Error:
java.lang.RuntimeException: Username is taken.
at com.example.inspire.data.ServerHandler$makeHttpRequest$stringRequest$5.onErrorResponse(ServerHandler.kt:75)
I do not know all the details, but it seems that the call ServerHandler.getInstance(context).makeHttpRequest( must be returning instantly (even before any HTTP requests are made).
Just put a logging statement after the call but before the return too see if that is really the case. The HTTP request is probably made later at some point (possibly in another thread), when the registerAccount function has long but exited (and so is the try/catch block defined within).
Due to the asynchronous feature in Volley callbacks, the Android Studio debugger has helped to confirm that registerAccount() has returned the result before makeHttpRequest() has done its job to communicate with the PHP server.
As registerAccount() has returned, throwing RuntimeException("Username is taken.") from makeHttpRequest() has no one left to catch its exceptions, which causes the exception unable to be caught.
In this case, catching exceptions sounds impossible, so I would just rather make a
Toast.makeText(
_context,
"Username already taken!",
Toast.LENGTH_LONG
).show()
instead of throwing exceptions...

Golang echo middleware redirect with empty page

I testing a redirect about echo when I just use the method to redirect it can show the login.html page successfully, but if I use a middleware test, it always shows empty page of login.html, what am I missing?
e.Group("*.html", func(next echo.HandlerFunc) echo.HandlerFunc { //1
return func(c echo.Context) error {
uri := c.Request().URL.String()
log.Println("uri:" + uri)
if uri != "/login.html" && uri != "/favicon.ico" {
c.Redirect(http.StatusSeeOther, "login.html")
return nil
}
return nil
}
})
e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))
e.GET("/aa", func(c echo.Context) error { //2
c.Redirect(http.StatusSeeOther, "login.html")
return nil
})
I see that you are missing the call of next to continue the request chain.
See the example here: https://echo.labstack.com/cookbook/middleware/
// ServerHeader middleware adds a `Server` header to the response.
func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderServer, "Echo/3.0")
return next(c)
}
}
See the return next(c), it continues to process the request throw all middlewares and eventually the login.html static handler.
As you are not calling, it stops the chain and does nothing.

Get the HTTPURLResponse from a Siesta Response

I'm fighting against REST API that performs a 304 redirect; what I need is to take the destination URL of the redirect and open it with the browser (I know, it's a little of a perversion). I successfully intercepted the redirect, thanks to this nice lad reversepanda:
https://github.com/bustoutsolutions/siesta/issues/210
but still I didn't figure out how to take the redirect url in the callback of the GET request (success or failure)
resource.load().onSuccess{ response in
//HERE I WOULD LIKE TO TAKE THE URL OF THE REDIRECT
//(if I print the response I can see the HTML of the destination web page where the user should land)
}.onFailure{ error in
//using 'completionHandler(nil)' in the 'willPerformHTTPRedirection' method of the delegate, brings me here
}
Any suggestion on how I could resolve this problem?
Thank you!
Take a look inside RequestChain.swift, it has some comments with example which can help. I believe you can do something like:
func redirectRequest() -> Request {
return self.yourAnotherRequest(onSuccess: {
}, onFailure: { error in
})
}
func yourRequest(request: Siesta.Request) -> Request {
return request.chained {
guard case .failure(let error) = $0.response,
error.httpStatusCode == 401 else {
return .useThisResponse
}
return .passTo(
self.redirectRequest().chained {
if case .failure = $0.response {
return .useThisResponse
} else {
return .passTo(request.repeated())
}
}
)
}
}
You can search more examples with keywords chained, useThisResponse and passTo in Siesta sources.
Please, let us know if it helps to solve your issue and it would be nice to see your final solution.

Break for loop inside a web request (in swift)

Inside my for loop i run a web request. Now if theres an error in the web request i want to break the for loop, however i get this error:
break is only allowed inside a loop or switch I've also tried naming my for loop like I've seen such as myLoopName : for(...) then calling it with break myLoopName but then i get the same error.
Here is an example of how my code looks currently
myLoopName : for(...) {
...
SRWebClient.POST(someUrlString)
.data(...)
.send({(response:AnyObject!, status:Int) -> Void in
//process success response
},failure:{(error:NSError!) -> Void in
//process failure response
println(error)
//where break must occur
})
}
How do i break the loop inside a web request?
I would do it using a function when dealing with closures and breaks:
func loop() {
SRWebClient.POST(someUrlString).data().send( { response, status in
loop()
},
failure:{ (error:NSError!) -> Void in
println(error)
}
}
I used return instead of break and it worked perfectly
func loop() {
SRWebClient.POST(someUrlString).data().send( { response, status in
loop()
},
failure:{ (error:NSError!) -> Void in
println(error)
return
}
}