I need to be able to handle special characters in a REST call. Specifically the . and / characters.
For example I have a GET route /api/division/{someDivision}. Now, calling this route with a parameter of /api/division/West Canada/ I get a return and everything works as expected. However, I need to be able to support other business divisions which have names such as "Southwest U.S." and "North/South America". Passing these parameters through my route returns a 404 via the api, since I presume, that the http handler thinks that the . and / characters make it think I'm referring to another domain or directory. Is there anyway to work around this so I can pass the needed parameter?
The route:
[HttpGet]
[Route("{division}/information")]
public IHttpActionResult DivisionInfo(string division)
{
...omitted for brevity
You could try setting your route up like this:
[HttpGet]
[Route("/api/information")]
public IHttpActionResult DivisionInfo(string division)
Then you can call GET http://website.com/api/information?division=text.with/special./characters.
try adding [FromUri] before the param:
Route("api/Person/{ID}/[FromUri]{UserName}")]
Related
I am Using the play Framework 2.7.x
I have a Formular on my controller.list() with a view, let's call it "index". After you click "send" it open's controller.add() where it dos some stuff and then redirects back to controller.list(). If there was an error in the formular (a requiered field was empty) I need the queryString, which was send to controller.add() also redirected to controller.list()
The problem ist that if I do stuff like just passing the request, i get an error that it's not possible to add arguments.
public Result list(Http.Request request)
{
// .... stuff with foo, while foo is an Form<foo> Object
// ... foo.bindFromRequest(request)
ok(views.html.index.render(foo))
}
public Result add(Http.Request request)
{
// not allowed to add request as an argument. only empty is allowed.
return Results.redirect(controllers.routes.Controller.list(request));
}
I would like to just redirect the Form object, so I can handle the error in the controller.list() and not have to generate an extra view for the controller.add(). If I do everything inside controller.list() there is no problem with this code, but I like to use the controller.add() method instead.
Is the an option? except passing every querystring key and value by hand.
After I searched yesterday the half the day, I found something interessting today.
you are not allowed to use a default parameter with =. You have to use an optional default parameter with ?= inside the routes!!!!!
you can implement QueryStringBindable so it's a bit easier to bind the query String. But you still have to bind them "by hand".
I am playing around with REST services in a Spring Boot environment. I have a question about the URI and naming conventions.
I have (currently) the following three mappings in my controller implementation...
#RequestMapping(method=RequestMethod.GET, value= "/v1/accounts")
public List<Account> getAllAccounts() {
return accountService.getAllAccounts();
}
#RequestMapping(method=RequestMethod.GET, value="/v1/accounts/{accountId}")
public Account getAccount(#PathVariable int accountId) {
return accountService.getAccount(accountId);
}
#RequestMapping(method=RequestMethod.GET, value="/v1/accounts/")
public Account getAccount(#RequestParam("shortName") String shortName) {
return accountService.getAccount(shortName);
}
These currently "work", but I have a question/concern about the getAccount(String) method. If I simply use the path "v1/accounts", the compiler seems to be unable to differentiate this from the URI for getAllAccounts(). So, I added the trailing '/', and the request now looks like...
/v1/accounts/?shortName=foo
However, it seems like the two requests should be...
/v1/accounts
and
/v1/accounts?shortName=foo
But, as already identified, changing the third request mapping to remove the trailing '/' results in compile-time errors.
Any input on either (a) how to eliminate the trailing '/' without running into compile-time errors, or (b) the advisability of incorporating the trailing '/' "just" to have both REST services exposed (I'm concerned about "what happens when the 3rd service is needed")?
After talking with one of our front-end devs, who said it's preferable to only deal with one return type (i.e., List vice List and Account), things simplified to...
#RequestMapping(method=RequestMethod.GET, value="/v1/accounts")
public List<Account> getAccount(#RequestParam("shortName") Optional<String> shortName) {
return (shortName.isPresent()) ? accountService.getAccount(shortName.get())
: accountService.getAccounts();
}
I've seen some concerns about the use of "Optional" as a parameter type, but it does seem to (a) clean up the REST endpoint's URI, and (b) is not horrific in the controller implementation.
Unless someone points out the "big, unanticipated nightmare" associated with this approach, I think I'll run with this
My middleware need is to:
add an extra query param to requests made by a REST API client derived from GuzzleHttp\Command\Guzzle\GuzzleClient
I cannot do this directly when invoking APIs through the client because GuzzleClient uses an API specification and it only passes on "legal" query parameters. Therefore I must install a middleware to intercept HTTP requests after the API client prepares them.
The track I am currently on:
$apiClient->getHandlerStack()-push($myMiddleware)
The problem:
I cannot figure out the RIGHT way to assemble the functional Russian doll that $myMiddleware must be. This is an insane gazilliardth-order function scenario, and the exact right way the function should be written seems to be different from the extensively documented way of doing things when working with GuzzleHttp\Client directly. No matter what I try, I end up having wrong things passed to some layer of the matryoshka, causing an argument type error, or I end up returning something wrong from a layer, causing a type error in Guzzle code.
I made a carefully weighted decision to give up trying to understand. Please just give me a boilerplate solution for GuzzleHttp\Command\Guzzle\GuzzleClient, as opposed to GuzzleHttp\Client.
The HandlerStack that is used to handle middleware in GuzzleHttp\Command\Guzzle\GuzzleClient can either transform/validate a command before it is serialized or handle the result after it comes back. If you want to modify the command after it has been turned into a request, but before it is actually sent, then you'd use the same method of Middleware as if you weren't using GuzzleClient - create and attach middleware to the GuzzleHttp\Client instance that is passed as the first argument to GuzzleClient.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Description;
class MyCustomMiddleware
{
public function __invoke(callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
// ... do something with request
return $handler($request, $options);
}
}
}
$handlerStack = HandlerStack::create();
$handlerStack->push(new MyCustomMiddleware);
$config['handler'] = $handlerStack;
$apiClient = new GuzzleClient(new Client($config), new Description(...));
The boilerplate solution for GuzzleClient is the same as for GuzzleHttp\Client because regardless of using Guzzle Services or not, your request-modifying middleware needs to go on GuzzleHttp\Client.
You can also use
$handler->push(Middleware::mapRequest(function(){...});
Of sorts to manipulate the request. I'm not 100% certain this is the thing you're looking for. But I assume you can add your extra parameter to the Request in there.
private function createAuthStack()
{
$stack = HandlerStack::create();
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
return $request->withHeader('Authorization', "Bearer " . $this->accessToken);
}));
return $stack;
}
More Examples here: https://hotexamples.com/examples/guzzlehttp/Middleware/mapRequest/php-middleware-maprequest-method-examples.html
I am a novice with vertx so maybe I am doing something wrong. I am trying to implement the following routes:
router.get("/api/users/").handler(this::getUsers);
router.route("/api/users/:username*").handler(this::checkUsername);
router.get("/api/users/:username/").handler(this::getUser);
router.put("/api/users/:username/").handler(this::addUser);
router.get("/api/users/:username/assignments/").handler(this::getAssignments);
router.post("/api/users/:username/assignments/").handler(this::addAssignment);
router.route("/api/users/:username/assignments/:assignmentId/").handler(this::checkAssignmentId);
router.get("/api/users/:username/assignments/:assignmentId/").handler(this::getAssignment);
Is this the correct way to avoid duplicating this logic in all handlers?
I am trying to chain handlers, where the checkUsername handler reads the username parameter from the path, tries to find a corresponding user, and puts that user in the context. If no user is found, a statuscode 400 is returned. Otherwise the next handler is called. I would like to apply the same principle to the assignmentId parameter.
While trying to implement this, I believe I found a problem with the path, more specifically the trailing slash and star. The documentation states that trailing slashes are ignored. This is not the behavior when there is a parameter in the path. In that case the trailing slash matters. If the path definition contains one and the request does not, vertx returns a 404. It does not make a difference whether or not the parameter is at the end of the path or in the middle.
The same goes for paths ending with a star. This functionality does not work when the path contains a parameter.
You can use a regular expression to avoid duplication of the checkUsername validation check. What I would do is I would have a method like this to check if the username is valid:
private void checkUsername(RoutingContext routingContext){
//The "param0" is the capture group of the regular expression. See the routing config below.
if (isValidUsername(routingContext.request().getParam("param0"))){
routingContext.next();
} else {
routingContext
.response()
.setStatusCode(400)
.end();
}
}
To check the assignment ID I would do something similar:
private void checkAssignmentId(RoutingContext routingContext){
if (isValidAssignmentId(routingContext.request().getParam("assignmentId"))){
routingContext.next();
} else {
routingContext
.response()
.setStatusCode(400)
.end();
}
}
Try to avoid trailing slashes in your paths. I would change the routing handler assignments to be something like this:
router.get("/api/users").handler(this::getUsers);
//By the way, you really want to be using a POST request when adding users just to stick to the principles of REST.
//When you are sending a POST request there is no need to put the username in the URI. You can have it in the request body.
//Please ensure you validate this username using the same validation helper used in your other validations.
router.post("/api/users").handler(this::addUser);
//Use regular expression to match all "/api/users/:username*" URIs
router.routeWithRegex("\\/api\\/users\\/([^\\/]+)").handler(this::checkUsername);
router.get("/api/users/:username").handler(this::getUser);
router.get("/api/users/:username/assignments").handler(this::getAssignments);
router.post("/api/users/:username/assignments").handler(this::addAssignment);
router.route("/api/users/:username/assignments/:assignmentId").handler(this::checkAssignmentId);
router.get("/api/users/:username/assignments/:assignmentId").handler(this::getAssignment);
I'm using the Dojo datagrid client side, it works well and according to documentation it generates the following GET request when clicking on the column header:
GET http://localhost:8080/books/rest/books?sort(+isbn)
Problem is that I can't interpret the query parameter "sort(+isbn)" on the server side using the Apache Wink framework, because there's no value set for it. E.g. I'd expect something like "sort=+isbn" instead.
Here's my server side code:
#Path("/books")
public class BookServiceImpl implements BookService {
...
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getBook(#QueryParam("sort") String sortBy) {
System.out.println("Received Queryparam for sort is " + sortBy);
return "";
}
}
Since "sort(+isbn)" has no value assigned to it, it appears to be an invalid query parameter. Not sure why Dojo datagrid uses this convention.
Would appreciate help as to how to work around this on the Java side, ideally using Wink or another mechanism to process GET requests.
Try to use #Context UriInfo to get the full uri info, call to UriInfo.getQueryParameters to get all query params. I believe sort(+isbn) will be there.