Jersey HTTP Delete,Put Response Status: 405 (Method Not Allowed) - java

Day 1: Added below rest endpoint for delete operation.
#Path("/company/v1/department")
#Component
public class ManageResource {
#DELETE
#Path("/{identifier}/{identifier_value}/employee")
public void delete(#PathParam("identifier") String identifier,
#PathParam("identifier_value") final String identifierValue,
#QueryParam("age") final String age) {
//delete operation
}
}
I was able to invoke DELETE endpoint using postman with below request:
DELETE: http://localhost:8080/company/v1/department/name/baner/employee?age=50
Day 2: Added below rest endpoint for the update operation in the same resource.
#Path("/company/v1/department")
#Component
public class ManageResource {
#DELETE
#Path("/{identifier}/{identifier_value}/employee")
public void delete(#PathParam("identifier") String identifier,
#PathParam("identifier_value") final String identifierValue,
#QueryParam("age") final String age) {
//delete operation
}
#PUT
#Path("/empid/{value}/employee")
#Consumes(MediaType.APPLICATION_JSON)
public void update(#PathParam("value") final String identifierValue,
#RequestBody final EmployeeUpdateRequest request) {
//update operation
}
}
After adding this new endpoint, I am able to invoke PUT using postman with below request:
PUT: http://localhost:8080/company/v1/department/empid/epid-123/employee
{
//Json request body
}
But when I try to invoke Delete endpoint it is giving me 405 (Method Not Allowed) error.
If I comment my new Put method, then the Delete method works fine.
Also, if I replace Path for Put method to "/{identifier}/{identifier_value}/employee" then both Delete and Put method works fine.
I am using Jersey 1.19 with tomcat.
Can someone help me with this?

Your Paths are in conflict with each other. Let me try to explain:
DELETE = /{identifier}/{identifier_value}/employee
PUT = /empid/{value}/employee
That means when we evaluate the path from left to right, we can either have
{identifier} which is anything or
"empid" which is a fixed string
Jersey always tries to find the "most perfect" match for a REST endpoint. It does so by evaluating the path from left to right.
Fixed strings always take precedence before random variables!
Basically that means when you want to call a DELETE, you cannot have the value "empid" for the variable "{identifier}" because then you are already out-of-scope
So the DELETE call to
http://localhost:8080/company/v1/department/empid/empid-123/employee
will not work as Jersey had to make a decision whether "empid" in the request matches "{identifier}" (DELETE) or "empid" (PUT). And as i tried to explain above, fixed strings take a higher priority.
In contrast, any other DELETE request where
http://localhost:8080/company/v1/department/{identifier}/empid-123/employee
and
{identifier} != "empid"
works.
Possible solution:
make your rest endpoints resource-oriented
DELETE:
/employee/{employee-id}
PUT:
/employee/{employee-id}
Notice how the endpoints are identical, since other than the ID in most systems, no information is needed to identify an entity.

Related

Java DeleteMapping Method Not Allowed

I'm trying to create an api to delete a certain ID from the storage;
Here's my code.
API Controller:
#DeleteMapping("{cId}")
#ResponseStatus(HttpStatus.OK)
public String delete(#PathVariable String cId) {
compareService.delete(cId);
return "redirect:/compare";
}
Service:
public void delete(String cId) {
compareLogic.delete(cId);
}
Logic:
public void delete(String cId){
System.out.println("A: " + sessionModel.getCIds());
List<String> update = sessionModel.getCIds();
update.remove(new String(cId));
System.out.println("B: " + sessionModel.getCIds());
}
However when I execute the api it shows
{
success: false,
warning: false,
error: "405",
error_description: "Method Not Allowed"
}
Are there any possible reasons by just looking at the code?
Many thanks,
Just I have tired with simple code snippet , Could you please try to understand and (Try to follow my suggestion as well )
When you hit from browser side (From Address Bar), it won't work for POST/PUT/DELETE calls , it is actually working from browser, if you try to typing in address bar then it is a GET request then it will not supported to the other format
Just I have added two screenshot I have tired with Browser and PostMan
First I have tired with POSTMAN (it is working perfectly)
Second I have tired with Browser (It will throw not supported
exception )
I have tired with small code snippet just copy from your code and remove element from list
#DeleteMapping("{cId}")
public String delete(#PathVariable String cId) {
List<String> arr=new ArrayList<String>(3);
arr.add("A");
arr.add("B");
arr.add("C");
arr.remove(cId);
for (String string : arr) {
System.out.println(string);
}
return "redirect:/compare";
}
The reason for this error is sending the request with a non-supported method. 405 Status Code indicates that the server doesn't support the method or verb sent in the request.
Could you also provide the API call details like HTTP method and relative path ? recheck your API details, make sure you are using correct HTTP method.

How i can pass a list<String> to the server with url

I am trying to pass the List of String from one server to the another server in spring boot.
How i can get that list at the another server?
The code i have tried-
public void addNewMostPopular(List<String> totalList){
try {
HttpHeaders httpHeaders = getHttpHeaders();
HttpEntity<String> httpEntity = new HttpEntity<String>(null, httpHeaders);
ResponseEntity responseEntity = restTemplate.exchange(
BASE_URL + "addMostPopular/"+new ArrayList<>(totalList), HttpMethod.POST, httpEntity,TrendingCategoryDTO.class);
}
and at server side i tried to get like-
#RequestMapping(value="/addMostPopular/[{totalList}]", method = RequestMethod.POST)
public void addMostPopularProduct( #PathVariable List<String> totalList) {}
Past long object in the url is a bad praxis, thats because spring url interpreter has a maximun lenght, so if you pass more than 2048 or 4096 char in some cases your request will return Response 400 bad request and won't execute anycode on your spring server.
After this aclaration, is there any option to pass a list? Yes, of course! But we need use #RequestBodylike this:
#PostMapping("/addMostPopular")
public void addMostPopularProduct(#RequestBody List<String> totalList) {
// Your function
}
Now we need to add to our other server the List we want to pass to this request in the body of the request.
If you like to pass a List of values in the url one possibility is to pass them as url parameters.
You have to create a link similar to the followings:
http://youserver/youraction?param=first&param=second&param=third
or
http://youserver/youraction?param=first,second,third
Your controller in spring must be something like
#Controller
public class MyController {
#GetMapping("/youraction")
public String yourAction(#RequestParam("param") List<String> params) {
// Here params is tre list with the values first, second, third
}
}
This action is able to handle both kind of requests that I wrote before.
There are many ways to pass infomation between servers.
The simple way is to initiate an http request, based on your request method get or post put the parameters to the appropriate location : reuqest header or request body. You can do like #Davide Lorenzo MARINO.
Or use a message queue, like ActiveMq.
In the case of the same registry center, you can also use #feign to resolve it.

Java EE 7 - Using #DELETE or #PUT with an entity body

I have a service method which looks like this
public void deleteData(Data data) {
this.dataDao.deleteData(data);
}
Data class have several fields in it. Somethig like this
private String name;
private String category;
private String discriminator;
private String description;
private String appName;
// getters & setters
I need to write a rest method for this. I was thinking to write something like this
#DELETE
#Path("/deleteData")
public Response deleteData(Data data) {
// implementation
}
The problem is that using #DELETE with entity body is not recommended or widely used.
My question is if it's ok to use #PUT instead of #DELETE? I can't change the service method implementation so that's not an option. What's the next best alternative here?
UPDATE
In dataDao.deleteData() method, finding an object is not done by object's ID. It looks something like this:
DataEntity entity = this.findDataByNameAndAppName(data.getName(), data.getAppName());
I decided to do something like this:
#DELETE
#Path("/deleteDataset")
public Response deleteDataset(#QueryParam("name") String name,
#QueryParam("appName") String appName) {
// implementation...
}
I didn't find any example of #DELETE method with #QueryParam, though. All examples was using #PathParam instead.
Well, DELETE is meant for... deleting stuff. So stick to that (without body).
You could either delete a resource using its unique identifier sent as a path parameter:
DELETE /resources/{id} HTTP/1.1
Host: example.org
If you need to delete multiple resources, you could consider query parameters to filter a collection of resources and then delete the resources that match such criteria:
DELETE /resources?name=foo&category=bar HTTP/1.1
Host: example.org

Retrieve/Change url parameter in Action

I make a call from my frontend to the userPrivateProfile controller.The route is /api/user/private/:id so let's say I make a call at /api/user/private/65. Before I excecute the controller the request is intecepted by SecurityAuthAction where I make sure that the request headers have the token and if that's the case I want to change the :id to something different.
Controller.java
#With(SecurityAuthAction.class)
public Result userPrivateProfile(Long id) {
//LOGIC
}
SecurityAuthAction.java
public Promise<SimpleResult> call(Http.Context ctx) throws Throwable {
String[] authTokenHeaderValues = ctx.request().headers()
.get(AUTH_TOKEN_HEADER);
if ((authTokenHeaderValues != null) && (authTokenHeaderValues.length == 1) && (authTokenHeaderValues[0] != null)) {
Long userId = sessionService
.findUserByToken(authTokenHeaderValues[0]);
ctx.args.put("id",userId.toString());
return delegate.call(ctx);
}
My problems are
that I cannot retrieve the :id specified from the original call using ctx
Since I cannot find where the request parameter is I cannot change it as well
I tried iterating through the ctx.args Map but I didn't find something there.The output is:
ROUTE_VERB ROUTE_
ACTION_METHOD
ROUTE_CONTROLLER
ROUTE_COMMENTS
ROUTE_PATTERN
GET
userPrivateProfile
controllers.Controller
/api/user/private/$id<[^/]+>
Thanx for your help :)
Unfortunately the Play Framework (certainly in version 2.1) does not give you easy access to URL query parameters when performing action composition. This discussion on the Play Google group may be of interest to you. One workaround mentioned there is to parse the URL in SecurityAuthAction to get the value of the id query parameter. However this is a little messy, and doesn't help you with the next part of your problem, which is changing the id before it gets to the downstream action.
Changing the details of the request as it's being handled by the server seems uncommon and wrong to me. Typically if you wanted to change what a client is requesting, you'd issue a HTTP 303 response redirecting them to the URL you want them to go to. But this doesn't feel like a situation for redirection. What I reckon you should do is just push your call to sessionService down to your main controller class:
SecurityAuthAction.java
public Promise<SimpleResult> call(Http.Context ctx) throws Throwable {
if (authorisation token header is present in request) {
return delegate.call(ctx);
}
return unauthorised();
}
Controller.java
#With(SecurityAuthAction.class)
public Result userPrivateProfile(Long id) {
// We've already established that an auth token header is present in the request
final String authToken = ctx.request().headers().get(AUTH_TOKEN_HEADER)[0];
final Long userId = sessionService.findUserByToken(authToken);
// TODO: Proceed...
}
If userId is something that you need all over your application, then it might be a candidate for inclusion in your application's cookie.

Restlet with many #Get in my server side

So I am new with restlet. I am creating a Android application that can communicate with a GAE server (with objectify DB)
I Did this very good tutorial to learn:
http://www.tutos-android.com/webservice-rest-android-appengine-restlet-objectify
It's working very well but do very little.
Onely 2 methods:
public interface UserControllerInterface {
#Put
void create(User user);
#Get
Container getAllUsers();
}
For my application its more complicated so I add many more methods:
public interface UserControllerInterface {
#Put
public void createUser(ObagooUser user);
#Put
public void createMessage(ObagooUser user, String message);
#Put
public void updateMessage(ObagooMessage message);
#Get
public List<ObagooUser> getAllUser();
#Get
public ObagooUser getUserById(String id);
#Get
public List<ObagooMessage> getAllMessage();
#Get
public List<ObagooMessage> getAllMessageFromSender(ObagooUser sender);
#Get
public ObagooMessage getFreeMessage(ObagooUser user);
}
Each of these mothds working server side (I tested with Junit).
Now I am coding the android part and I am having problems.
When I do a simple call to getAllMessage() I get an error:
java.lang.IllegalArgumentException: id cannot be zero
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:44)
at com.googlecode.objectify.ObjectifyFactory.typedKeyToRawKey(ObjectifyFactory.java:269)
at com.googlecode.objectify.impl.ObjectifyImpl.find(ObjectifyImpl.java:159)
at com.googlecode.objectify.impl.ObjectifyImpl.find(ObjectifyImpl.java:183)
at com.obagoo.dao.ObagooUserDAO.getUserById(ObagooUserDAO.java:43)
at com.obagoo.controller.ObagooController.getUserById(ObagooController.java:47)
It's going in the wrong method (getUserById).
I put a break point in my getAllMessage and it's going in, but it is also going in other methods.
If I test many times, sometimes it's calling, createUser or another random method.
Do you see what I am doind wrong?
Adding the getAllMessage code:
public List<ObagooMessage> getAllMessage() {
// logger.debug("Getting all Obagoo Messages");
List<ObagooMessage> msg = new ArrayList<ObagooMessage>();
Objectify ofy = ObjectifyService.begin();
Query<ObagooMessage> q = ofy.query(ObagooMessage.class);
for (ObagooMessage u : q) {
msg.add(u);
}
return msg;
}
In the examples that I've seen, its always shown that you should separate the controller/resource handling the URI for the list resource from the single item (id/name based) resource. So you would have something like:
router.attach("/users", UsersController.class);
router.attach("/users/{id}", UserController.class
router.attach("/messages", MessagesController.class);
Notice the plural naming on the first class: UsersController, and singular naming on the the second class: UserController. The first class would handle cases where no id was being provided, such as a get of all users. Also, note when the id is provided in the URI, it can be automatically mapped into an id field on the class. So the Get method has no parameters on the method call.
As for handling a subset, then for messages from a specific user, that could be handled with query parameters. For instance when calling via a URI with /messages?sender=id, the MessagesController.class would use the following in the method handling the Get:
Form queryParams = getRequest().getResourceRef().getQueryAsForm();
String id = queryParams.getFirstValue("sender");
Hope that helps. I'm no expert, so anyone feel free to correct me.
As error says: you are creating a key with zero Id.
My gues is that your ObagoMessage Id field is long? You should make it Long. Primitive long Id values are not autogenerated, while object type Long are. See the docs.

Categories