Capturing request body content in Grails 4 for logging - grails

Building out a new API using Grails 4, and I would like to have the option of logging the full request (headers, method, content, etc). I can see the request in an Interceptor, but the content can only be read once (using HttpServletRequest.getInputStream()), so reading it in the Interceptor prevents the content from being available in the Controller.
There are some similar questions about this on Stack Overflow already that address this need by using Grails Filters.
One reason I don't want to go down that path is that, according to the Grails docs, Filters are now considered deprecated (as of v3.0), and Interceptors should instead be used. Unfortunately, none of the solutions I can find work with Interceptors. I tried a couple of those solutions myself that involve wrapping the request inside of a HttpServletRequestWrapper to cache the body content and ran into the same issues as others with trying to get it to work with an Interceptor.
I have seen suggestions to use Java Servlet Filters, it's not obvious how that's different from Grails Filters, or if they should also be avoided.
Edit: As noted in comments below, I didn't mention that I'm using command objects, and the solution needs to work with those. I'm also using a number of other Grails features that I'm not sure will be affected by whatever solutions will be proposed, so if there are limitations to the proposed solution, it would be good to know about those.

Added PoC project: https://github.com/majkelo/grails4requestinterceptor
basically added an interceptor PROJECT/grails-app/controllers/testrequest/RequestModificatorInterceptor.groovy (modyfiyng all the requests):
package testrequest
class RequestModificatorInterceptor {
RequestModificatorInterceptor() {
match(controller: "*", action: "*")
}
boolean before() {
println "INSIDE INTERCEPTOR; BEFORE"
println request.properties
println request.JSON
request.JSON.before = "added"
true
}
boolean after() {
true
}
void afterView() {
// no-op
}
}
and test controller PROJECT/grails-app/controllers/testrequest/TestController.groovy:
package testrequest
import grails.converters.JSON
class TestController {
def index() {
println "INSIDE CONTROLLER"
println request.JSON
request.JSON.incontroller = true
render request.JSON as JSON
}
}
it's reading/modyfing POST request in interceptor AND in the controller. Example request:
curl --location --request POST 'http://localhost:8080/test' \
--header 'Content-Type: application/json' \
--data-raw '{"js":"on"}'```

Related

How to handle API callbacks in ASP.NET MVC (Helloworks API in my case)

As per their documentation from link https://docs.helloworks.com/v3/reference#callbacks
"With the HelloWorks API you can use callbacks to be notified about certain events. Currently we support a callback to be registered when a step is started, the cancellation of a workflow, and the completion of a workflow.
The callback URL will be called according to the following:
curl -X POST https://your.domain.com/some/path
-H "X-HelloWorks-Signature: {signature}"
-H "Content-Type: application/json"
-d "{payload}
I am not able to figure out how can I handle the callback in ASP.NET MVC 4.0. The callback returns data on JSON format. Once I receive the data, I can format it as per my need and can save to database. But how can I get the data in my controller? Guidance from experts on APIs are highly appreciated. Thanks in advance.
I am not able to figure out how can I handle the callback in ASP.NET MVC 4.0.
You need to have an api controller that accepts POST requests. That api endpoint is then called by the HelloWorks api. The fancy word to describe this mechanism is a Webhook. A nice introduction can be found here.
The very basic would be a controller like
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MyWebAPI.Controllers
{
public class WebHookController : ApiController
{
// POST: api/webhook
public void Post([FromBody]string value)
{
}
}
}
You will need to register the url https://yourwebsite.domain/api/webhook at the HelloWorks api so it knows where to send the data to.
You probably want to secure this endpoint so others cannot abuse this api. See the docs for some guidance.
For example, in your case you should check that a header named "X-HelloWorks-Signature" is send in the request to the endpoint. The value of that header is a hash that should match the value of a hash of the content you received. To calculate the hash code to match create a hash using the SHA-256 algorithm and base16-encode the result.
There is also documentation from Microsoft on how to create web apis
Peter your guidance worked. I appreciate that. It was straight forward, only the technical jargon are making it intimidating :). Below are the code that worked. I am still to secure it using signature.
[HttpPost]
public ActionResult Callback()
{
string rawBody = GetDocumentContents(Request);
dynamic eventObj = JsonConvert.DeserializeObject(rawBody);
Test newTest = new Test();
newTest.Response = "Bikram-1" + (string)eventObj.type;
var test = db.Tests.Add(newTest);
db.SaveChanges();
return Content("Success!");
}
private string GetDocumentContents(HttpRequestBase Request)
{
string documentContents;
using (Stream receiveStream = Request.InputStream)
{
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
documentContents = readStream.ReadToEnd();
}
}
return documentContents;
}

Grails 2.5.5: Unable to access the request body of a PUT request?

I'm attempting to implement a RESTful API using Grails 2.5.5, and I'm running into a few issues.
It appears that Grails does not automatically map any methods for the corresponding HTTP methods, so I'm editing UrlMappings.groovy.
For example, take the following URLs:
GET /v1/1/persons/ <--- List of persons
POST /v1/1/persons/ <--- Create a new person
PUT /v1/1/persons/1234 <--- Edit person with ID of 1234
These are my url mappings:
"/v1/$appId/$controller/$action?/$id?(.$format)?" {
namespace = "v1"
}
"/v1/$appId/$controller"(action: "save", method: "POST") {
namespace = "v1"
}
"/v1/$appId/$controller/$id"(action: "update", method: "PUT") {
namespace = "v1"
}
So now, the first mapping will handle the GET request in my example urls as well as other generic urls.
The second mapping will handle the second url from my example urls.
And lastly, the third mapping handles the third url from my example urls.
The issue I'm facing now is that my command object isn't getting bound properly for my PUT request. The POST request works fine however.
These are my methods:
def save(MyCommand cmd) {
// works great
}
def update(MyCommand cmd) {
// cmd properties are null
// params.id is bound though. So I'm getting the path variable.
}
As you can see, the logic is very simple.
But I'm completely stumped as to why I can't get the request body in the PUT method.
Additional question: How can I get the above urls to work in addition to this url?:
/v1/1/persons/1234/status
I tried the following mapping, but it does not seem to work:
"/v1/$appId/$controller/$id/$action" {
namespace = "v1"
}
It feels like I'm stuck in this URLMappings hell!

Request Header Setup in Swagger Integration With Spring Data Rest

I have a Swagger intgration setup for end points generated by Spring Data Rest in a Spring Boot project. The followings are related dependencies I have in the project:
compile "io.springfox:springfox-swagger2:2.6.1"
compile "io.springfox:springfox-swagger-ui:2.6.1"
compile "io.springfox:springfox-data-rest:2.6.1"
compile "io.springfox:springfox-bean-validators:2.6.1"
The header setup in Swagger for those end points are varied. For example, I have
curl -X GET --header 'Accept: application/x-spring-data-compact+json' 'http://localhost:8080/accounts'
where the accept or content type in the header should be application/json. How to have the right header setting?
You can tell Swagger about the content types that can be returned by the endpoint by using the #ApiOperation Swagger annotation. For example;
#ApiOperation(produces = "application/json,application/x-spring-data-compact+json")
Full example:
#RestController
class ExampleController
{
#ApiOperation(produces = "application/json,application/x-spring-data-compact+json")
#RequestMapping(value = "/byId", method = RequestMethod.GET)
public ExampleResponse getById(#RequestParam String id)
{
...
}
}
By doing this they will then appear in the dropdown.

Restful Controller in Zendframework 2 - how does its parameter work?

This is my unit test for create function :
public function testCreate() {
$this->routeMatch->setMatchedRouteName('restful');
$this->request->setMethod('POST')
->setContent('name=A');
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(403, $response->getStatusCode());
$this->assertArrayHasKey('id', $result);
}
And this is my function :
public function create($data) {
if (empty($data)) {
$this->response->setStatusCode(400);
return;
}
for ($i = 0; $i < count(self::$ideas); $i++) {
if (self::$ideas[$i]['name'] == $data['name']) {
$this->response->setStatusCode(404);
return;
}
}
//#todo: secure the API
self::$index++;
$tmpArray = array('id'=>self::$index, 'name'=>$data['name']);
$this->response->setStatusCode(403);
}
But it seems that the $data is always blank. Am I wrong at the part writing unit test ?
When I try to use curl POST with -d, the $data has value as what I post through curl. I'm a quite confused what is wrong here ?
Thanks for reading and looking forward to your answer :)
Answer
I've came up with my successful unit test http://pastebin.com/fwFe0Mi3
For more information, I use this module to implement restful controller
If you take a look at \Zend\Mvc\Controller\AbstractRestfulController method processPostData you will notice that the method create in your controller is given an array of the post params from the request object.
If you look at \Zend\Http\Request the $postParams property is populated by the setPost method.
Now the child class \Zend\Http\PhpEnvironment\Request (used by ZF2 when you are requesting something) that extends \Zend\Http\Request (above) on instantiation (__contruct method) calls the setPost method (above) giving it the $_POST array.
This means that eventually ZF2 internally feeds your controller's create method with the $_POST contents and not by parsing the request body.
Now to your code.
I don't think dispatch will do anything without you having set up the event framework first. Instead you can call the controllers execute method providing it with an MvcEvent. The MvcEvent needs to have the request you instantiated set.
Secondly, as described above you need to call the request's setPost and give it an array for the create method to work properly. (On the other hand PUT reads the data from the request body)
Try doing that and if you are still having trouble I will try and give you an example soon.

grails xml unmarshalling

Since Grails 1.1.x, they have supported XML and JSON unmarshalling for REST requests. I can't seem to get this working in version 2.1.0. Here is the relevant files from the example project I am using to test the functionality:
UrlMappings.groovy
static mappings = {
"/$action/$id?"(controller:'verification',parseRequest:true)
"/"(view:"/index")
"500"(view:'/error')
}
Tester.groovy
class Tester {
String name
String vendor
String toString() {
return "$name $vendor"
}
}
VerificationController.groovy
class VerificationController {
def save() {
Tester tester = new Tester(params.tester)
log.error "Tester = ${tester}"
log.error "Request XML = ${request.XML}"
}
}
XML send with REST call
<?xml version="1.0" encoding="utf-8"?>
<tester>
<name>Windows</name>
<vendor>Microsoft</vendor>
</tester>
When I pass the XML in, the controller log statements output this:
Tester = null null
Request XML = WindowsMicrosoft
My bewilderment would be in why it recognizes it as XML (by putting it in the XML field of the request) but won't parse it and put it in params as defined here
After trying for a few hours with the same problem I found a solution for me.
My problem was that I didn't had a content-type defined in my REST call.
So if you add content-type: application/xml to your rest call it should work.
I had the same problem, with almost exactly the same setup. I believe your problem could lie within the UrlMapping configuration. Initially I had the following.
"/rest/airport/$iata?"(controller: "airport", action: "restHandler", parseRequest: "true")
When I PUT/POST XML to that URI, it would show up correctly in request.XML, but it would never show up in the params object. I then realized I had the parseRequest boolean in quotes. Removing that fixed the issue.
"/rest/airport/$iata?"(controller: "airport", action: "restHandler", parseRequest: true)
Now, if your code is truly the same as what you have above, your problem may not be exactly the same. However, what might help is making some kind of change to your UrlMappings to see if you could get it working a different way.
At first I thought it might have been the single quotes you had, but that seemed to work fine either way I tried it. So, I would suggest just changing your URI mapping to something else and be very picky about the formatting. In the end it was just a minor formatting issue in the UrlMappings.groovy file that fixed my problem.

Resources