If I specify method="PUT" in a web form and also filter by this method in the route by giving
("/tickets/:id", method PUT updateTicket)
the form never gets to the handler.
The handler never even triggered. As soon as I change method to POST in the route and in the form then everything works as expected.
Any idea why?
Thanks.
Try not limiting your route to a certain method. Then you can do some debugging to see what's really going on:
updateTicket = do
meth <- getsRequest rqMethod
liftIO $ putStrLn $ "Request had method "++(show meth)
...
Then play around with your form and see what's actually happening.
Related
I'm building an API with Servant, and it seems to work pretty well. And in line with best practices I am writing some tests for it, following the official guide here. But I'm struggling with the part using Servant-Quickcheck.
I'm trying to use it to test that my API doesn't give any 500 errors, like this:
quickCheckSpec :: Spec
quickCheckSpec = describe "QuickCheck global tests for public API -" $ do
it "API never gives 500 error" $
withServantServer publicAPI (return server) $ \burl ->
serverSatisfies publicAPI burl args (not500 <%> mempty)
but unfortunately the test is failing. This is a surprise to me, given that I've not encountered such an error when testing manually - but hey, this is why we have automated tests, right? Especially with tools like QuickCheck which, as far as I understand, is supposed to zero in on "edge cases" that you might not think to test manually. (This is actually my first time using QuickCheck in any form.)
The problem is though that a test failure is useless unless you know what it failed on. And this is what Servant-QuickCheck does not seem to want to tell me. The only output I get regarding the test failure is this:
API never gives 500 error FAILED [1]
Failures:
src\\Servant\\QuickCheck\\Internal\\QuickCheck.hs:146:11:
1) QuickCheck global tests for public API - API never gives 500 error
Nothing
To rerun use: --match "/QuickCheck global tests for public API -/API never gives 500 error/"
Randomized with seed 1712537046
Finished in 10.8513 seconds
4 examples, 1 failure
(There are some unit tests run before, which all pass - the 10 seconds isn't all on the above test!)
This is rather bemusing, because as you can see there's nothing which indicates how the failure occurred. Since it's testing an API and, as far as I understand it, choosing valid routes at random to test with, it ought to tell me at which route it encountered the 500 error. Yet, as you can see, there is precisely nothing there.
(I'm developing the project with Stack btw, and a little bit below the above it says Logs printed to console, so I don't believe I'm missing any log files buried somewhere which could enlighten me. I haven't been able to find any in my .stack-work folder either.)
I've even changed my args so that it has the chatty field set to True, but I still only get the above.
The only clue I get, which isn't related to QuickCheck, is that my API uses Persistent and logs the database queries to the terminal, so I can see the queries that have been run - which I can in turn relate back to the route. However I don't particularly trust this, because the route that runs the query shown is definitely working in manual tests. (And it's not always the same query when it fails.) There are also a couple of simple routes that don't query the database, so the failure isn't necessarily related to the last database query printed (although needless to say, I've manually tested those routes too and they're fine).
I've even wondered if the problem might not be that QuickCheck hits my localhost server so many times in a row that it simply can't cope and errors out, but in that case I would expect this to be a common problem, remarked upon in the tutorial. (And the database logging output suggests that it in fact always fails on the very first "hit".)
Anyway, that's just speculation, and I realise that it's up to me to figure out why my API might be failing the tests, given that I didn't think it relevant to share any details of my actual API.
What I'm really asking is: why am I not told which routes were tested, and which ones passed/failed, so that I at least know exactly which route(s) to investigate? And is there any way to make Servant-Quickcheck show me that information?
Any answer to that would be much appreciated.
FURTHER INFORMATION: I've tried with #MarkusBarthlen's suggestion below, but it didn't give me any more information. However, it did point up that the Nothing which you can see in my console output is probably somehow key to this, because in Markus's example he gets a Just value holding the request information. Looking at line 146 of the source file referenced in the failure message, it's clearly the x there that apparently is a Maybe value. But I can't figure out what type it actually is, and what the significance is of it being Nothing in my case. Unfortunately the code is beyond my own Haskell experience/ability to make much sense of - I can see the x is read out of an MVar, which is populated somehow with the result of the test, but as I said I'm not able to make sense of the details. But I really need to know, not only why my test is failing, but why this particular x value is Nothing for me, and what that means in terms of the test. I would really appreciate an answer that explains this in a way I can understand.
From what I see, you could write a RequestPredicate as in https://github.com/haskell-servant/servant-quickcheck/blob/master/src/Servant/QuickCheck/Internal/Predicates.hs
for example notLongerThan.
I tested it with
not200b :: RequestPredicate
not200b = RequestPredicate $ \req mgr -> do
resp <- httpLbs req mgr
when (responseStatus resp == status200) $ throw $ PredicateFailure "not200b" (Just req) resp
return []
yielding a response of
Failures:
src/Servant/QuickCheck/Internal/QuickCheck.hs:146:11: 1)
QuickCheck global tests for public API - API never gives 200
Failed:
Just Predicate failed
Predicate: not200b
Request:
Method: "GET"
Path: /users
Headers:
Body:
Response:
Status code: 200
Headers: "Transfer-Encoding": "chunked"
"Date": "Fri, 22 Nov 2019 21:32:07 GMT"
"Server": "Warp/3.2.28"
"Content-Type": "application/json;charset=utf-8"
Body: [{"userId":1,"userFirstName":"Isaac","userLastName":"Newton"},{"userId":2,"userFirstName":"Albert","userLastName":"Einstein"}]
To rerun use: --match "/QuickCheck global tests for public API -/API
never gives 200/"
Randomized with seed 1026627332
Finished in 0.0226 seconds 1 example, 1 failure
I'm new to flask and currently converting an existing WSGI application to run through flask as long term it'll make life easier.
All requests are POST to specific routes however the current application inspects the post data prior to executing the route to see if the request needs to be run at all or not (i.e. if an identifier supplied in the post data already exists in our database or not).
If it does exist a 200 code and json is returned "early" and no other action is taken; if not the application continues to route as normal.
I think I can replicate the activity at the right point by calling before_request() but I'm not sure if returning a flask Response object from before_request() would terminate the request adequately at that point? Or if there's a better way of doing this?
NB: I must return this as a 200 - other examples I've seen result in a redirect or 4xx error handling (as a close parallel to this activity is authentication) so ultimately I'm doing this at the end of before_request():
if check_request_in_progress(post_data) is True:
response = jsonify({'request_status': 'already_running'})
response.status_code = 200
return response
else:
add_to_requests_in_progress(post_data)
Should this work (return and prevent further routing)?
If not how can I prevent further routing after calling before_request()?
Is there a better way?
Based on what they have said in the documents, it should do what you want it to do.
The function will be called without any arguments. If the function returns a non-None value, it’s handled as if it was the return value from the view and further request handling is stopped.
(source)
#app.route("/<name>")
def index(name):
return f"hello {name}"
#app.before_request
def thing():
if "john" in request.path:
return "before ran"
with the above code, if there is a "john" in the url_path, we will see the before ran in the output, not the actual intended view. you will see hello X for other string.
so yes, using before_request and returning something, anything other than None will stop flask from serving your actual view. you can redirect the user or send them a proper response.
I want to return some preformatted html in a snap application. However, when the handler below is served,
aPage :: Handler App App ()
aPage = do
writeText "<p>This is a page</p>"
The output is couched in < pre > tags.
...<body><pre><p>This is a page</p></pre></body> ...
Is there a simple way to add a verbatim string to the response body?
You don't.
As Carl pointed out in the comment to my question, it was not "escaped" to begin with. What I was seeing was the browsers rendition of the plaintext document. Simply sending a properly formatted document gives me what I was expecting.
aPage :: Handler App App ()
aPage = do
writeText "<!DOCTYPE html><html><head></head><body><p>This is a page</p></body></html>"
After fiddling with Blaze-html and Lucid, two libraries for generating html, I was sure some sort of formatting was going on under the hood and thought some sort of toHtmlRaw function was needed. Not at all the answer I was expecting.
I have a smallish Yesod application (using the scaffold). I'd like to add basic HTTP authentication to all requests. Here's what I tried so far:
I've read the docs on Yesod authentication, but there's unfortunately no backend supporting this.
isAuthorized would be great, but I can't see a way to read the headers there.
A WAI middleware would be elegant, but I can't find any documentation describing how to use one with a full Yesod application. It's also pretty clear that writing one is not completely trivial.
Was this already done? How should I approach this?
I've come up with a non-ideal solution: I prepend an action to all my handler functions. Maybe it'll be useful for someone, maybe someone can improve upon this. Here's the code:
httpBasicAuth :: Handler ()
httpBasicAuth = do
request <- waiRequest
case lookup "Authorization" (requestHeaders request) of
Just "Basic base64encodedusernameandpassword" -> return ()
_ -> do
setHeader "WWW-Authenticate" "Basic Realm=\"My Realm\""
permissionDenied "Authentication required"
And using it:
fooR :: Handler ()
fooR = httpBasicAuth >> do
sendResponseStatus status200 ()
I'll be more than happy to move the "accepted" checkmark if a better solution is posted.
I got an exception (used Prelude.head on an empty list) that made all the http-request after that return a 502/505 exception (and the happstack app prints "HTTP request failed with: send resource vanished (Broken pipe)" in stdout).
My question is this: What is the best practice for controlling exceptions in Happstack? Should I use something else than simpleHTTP or simply use Control.Exception.catch on the controller function?
It currently looks similar to the example in the Crash Course:
main :: IO ()
main = do
hSetEncoding stdout utf8
bracket (startSystemState (Proxy :: Proxy AppState)) createCheckpointAndShutdown $
\_control ->
simpleHTTP nullConf { port = 1729 } $ do
_ <- liftIO today
decodeBody policy
controller
where
createCheckpointAndShutdown control = do
createCheckpoint control
shutdownSystem control
What do you mean by, "the server went down"?
If you are handling a Request and do head [], that should only kill the thread that is handling that particular request. The server itself should continue running and processing other requests.
If you have found a way to kill the whole server, that is a bug and bug report / test case would be highly appreciated.
If only the current thread is being killed, and you want to catch that exception, then I think you need to use MonadPeelIO,
http://hackage.haskell.org/packages/archive/monad-peel/0.1/doc/html/Control-Monad-IO-Peel.html
Someone has submitted a patch for that, but it has not been reviewed/merged yet.