Some doubts about the use of parameters into a request toward a REST web service? - spring

I am pretty new in Spring and in REST web services and I have the following dout following a tutorial that show how to implement a RESTful web service using Spring MVC.
So, into a controller class I have this method:
#Controller
#RequestMapping("/api/categories")
public class CategoryRestController {
#RequestMapping
#ResponseBody
public CategoryList getCategories(#RequestParam("start") int start, #RequestParam("size") int size ) {
List<Category> categoryEntries = categoryService.findCategoryEntries(start, size);
return new CategoryList(categoryEntries);
}
}
This method handle HTTP GET request toward the resoruce /api/categories and return the retrieved list into JSON format (I think that it depends by the content negotiazion: if the caller put the Accept header as JSON the method return the result in JSON format, is it right?)
By the way my doubt is related the HTTP request shown in the tutorial, infact it do:
http://localhost:8080/springchocolatestore/api/categories?start=0&size=2
that is handled by the previous controller method to return a paginated list (that could be hude) in JSON format, infact I retrieve the following output:
{
"categories": [
{
"links": [
{
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/1",
"variables": [],
"templated": false,
"variableNames": []
}
],
"name": "Truffles",
"description": "Truffles",
"id": {
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/1",
"variables": [],
"templated": false,
"variableNames": []
}
},
{
"links": [
{
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/2",
"variables": [],
"templated": false,
"variableNames": []
}
],
"name": "Belgian Chocolates",
"description": "Belgian Chocolates",
"id": {
"rel": "self",
"href": "http://localhost:8080/springchocolatestore/api/categories/2",
"variables": [],
"templated": false,
"variableNames": []
}
}
]
}
Ok, so in the request I specify the pagination parameter by categories?start=0&size=2
My doubt is related to the user of this parameter. From what I have understand (but maybe it could be wrong) the use of the parameter is against the RESTful principles. Is it true or am I mising something?
Or maybe in this specific case are valid because the parameter are not specifing an object (that have to be returned into my JSON output) but are only related to some options?
I mean that maybe I can't use parameter to specify a specific object, something like this:
// RETRIEVE THE PRODUCT WITH ID=1
http://localhost:8080/springchocolatestore/api/producs?product=1
So I think that the previous is not following the RESTfull standard because I am specifing a product object with a parameter and not accessing to it as a resource, so I have to do in this way:
http://localhost:8080/springchocolatestore/api/producs/1
Can you give me some clarification?
Tnx

REST doesn't have much to do with URLs, and using request parameters is not unRESTful.
But I agree that path variables are generally used to identify a specific resource, and parameters are generally used for search or pagination parameters.

Related

Detect Django rest API conditions for accepting data before submitting

I've an API end point that accepts multiple choices to select special situations in a person page, like: disabilities or chronic disease. Django rest framework requires a list and gives this error when I send other type like string.
"special_situation":["Expected a list of items but got type \"str\"."]
However when I check the options of the endpoint I don't get an indication of that requirement:
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"name": "Special Situation List",
"description": "API endpoint that allows special situations to be viewed or edited.",
"renders": [
"application/json",
"text/html"
],
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"actions": {
"POST": {
"url": {
"type": "field",
"required": false,
"read_only": true,
"label": "Url"
},
"name": {
"type": "string",
"required": true,
"read_only": false,
"label": "Name",
"max_length": 50
}
}
}
}
I'm using HyperlinkedModelSerializer and endpoint is called at:
http://127.0.0.1:8000/myapi/person/
and
http://127.0.0.1:8000/myapi/special_situation/
The end point is linked from person endpoint where I'm sending values based on the options of special_situation, the options for person end point also doesn't show the list requirement:
"special_situation": {
"type": "field",
"required": false,
"read_only": false,
"label": "Special situation"
}
In the person module I've:
special_situation = ParentalManyToManyField('needy.SpecialSituation', blank=True, verbose_name='special situation')
And for SpecialSituation:
#register_snippet
class SpecialSituation(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)
panels = [
FieldPanel('name'),
]
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'special situations'
I thought the "type": "field" indicates a list it self but it's not acceptable in other endpoints without multiple choices. like in the charity end point.
"charity":["Incorrect type. Expected URL string, received list."]
full source code is availbe at:
https://gitlab.com/uak/needy/-/tree/api_development
so the issue is that there is no indication that this end point requires a list except in the error message. I would like to be able to detect accepted type before I submit data to be able to dynamically construct a form in my GUI app.
I know I can manually detect the field name and create a list but would like a dynamic/automated without hard coding fields with different requirements.

Spring Paging with custom paging links

When I pass through pageable to my spring rest controller method the method then returns a page of people and returns paging links like the following:
_links": {
"first": {
"href": "http://localhost:8080/people?page=1&size=1000"
},
"last": {
"href": "http://localhost:8080/people?page=1&size=1000"
},
"self": {
"href": "http://localhost:8080/people?page=1&size=1000"
}
}
Is there code I can call within the page object that is being returned to change these links specifically I would like to change http to https.
Where and how would I do this. Any help you can give would be greatly appreciated. Thank you.

Spring Integration Java DSL: How to loop the paged Rest service?

How to loop the paged Rest service with the Java DSL Http.outboundGatewaymethod?
The rest URL is for example
http://localhost:8080/people?page=3
and it returns for example
"content": [
{"name": "Mike",
"city": "MyCity"
},
{"name": "Peter",
"city": "MyCity"
},
...
]
"pageable": {
"sort": {
"sorted": false,
"unsorted": true
},
"pageSize": 20,
"pageNumber": 3,
"offset": 60,
"paged": true,
"unpaged": false
},
"last": false,
"totalElements": 250,
"totalPages": 13,
"first": false,
"sort": {
"sorted": false,
"unsorted": true
},
"number": 3,
"numberOfElements": 20,
"size": 20
}
where the variable totalPages tells the total pages amount.
So if the implementation
integrationFlowBuilder
.handle(Http
.outboundGateway("http://localhost:8080/people?page=3")
.httpMethod(HttpMethod.GET)
.expectedResponseType(String.class))
access one page, how to loop all the pages?
The easiest way to do this is like wrapping the call to this Http.outboundGateway() with the #MessagingGateway and provide a page number as an argument:
#MessagingGateway
public interface HttpPagingGateway {
#Gateway(requestChannel = "httpPagingGatewayChannel")
String getPage(int page);
}
Then you get a JSON as a result, where you can convert it into some domain model or just perform a JsonPathUtils.evaluate() (based on json-path) to get the value of the last attribute to be sure that you need to call that getPage() for the page++ or not.
The page argument is going to be a payload of the message to send and that can be used as an uriVariable:
.handle(Http
.outboundGateway("http://localhost:8080/people?page={page}")
.httpMethod(HttpMethod.GET)
.uriVariable("page", Message::getPayload)
.expectedResponseType(String.class))
Of course, we can do something similar with Spring Integration, but there are going to be involved filter, router and some other components.
UPDATE
First of all I would suggest you to create a domain model (some Java Bean), let's say PersonPageResult, to represent that JSON response and this type to the expectedResponseType(PersonPageResult.class) property of the Http.outboundGateway(). The RestTemplate together with the MappingJackson2HttpMessageConverter out-of-the-box will do the trick for you to return such an object as a reply for the downstream processing.
Then, as I said before, looping would be better done from some Java code, which you could wrap to the service activator call. For this purpose you should daclare a gateway like this:
public interface HttpPagingGateway {
PersonPageResult getPage(int page);
}
Pay attention: no annotations at all. The trick is done via IntegrationFlow:
#Bean
public IntegrationFlow httpGatewayFlow() {
return IntegrationFlows.from(HttpPagingGateway.class)
.handle(Http
.outboundGateway("http://localhost:8080/people?page={page}")
.httpMethod(HttpMethod.GET)
.uriVariable("page", Message::getPayload)
.expectedResponseType(PersonPageResult.class))
}
See IntegrationFlows.from(Class<?> aClass) JavaDocs.
Such a HttpPagingGateway can be injected into some service with hard looping logic:
int page = 1;
boolean last = false;
while(!last) {
PersonPageResult result = this.httpPagingGateway.getPage(page++);
last = result.getLast();
List<Person> persons = result.getPersons();
// Process persons
}
For processing those persons I would suggest to have separate IntegrationFlow, which may start from the gateway as well or you can just send a Message<List<Person>> to its input channel.
This way you will separate concerns about paging and processing and will have a simple loop logic in some POJO method.

What's the correct way to present paged resources with HAL?

This sounds like a rookie question, but I'm wondering what's the best way to present paged resources with HAL format? Right now I'm using Spring HATEOAS API to convert Page object into resource PagedResourcesAssembler#toResource(Page<T>, ResourceAssembler<T,R>). This results in the following output:
{
"_links": {
"self": {
"href": "http://example.org/api/user?page=3"
},
…
}
"count": 3,
"total": 498,
"_embedded": {
"users": [
{
"_links": {
"self": {
"href": "http://example.org/api/user/mwop"
}
},
"id": "mwop",
"name": "Matthew Weier O'Phinney"
}
]
}
}
Everything works fine but the only problem is the collection being returned is under _embedded field and has the class name, so the client has to know this class name as well right? Would it be better to just return the collection under content like non-HAL format? If yes how should I achieve it using Spring HATEOAS?
That's not a problem, that's the way _embedded is defined in the HAL specification.
users is not a class, it's a link relation that will allow clients to actually find the collection it was asking for in the first place (e.g. using a JSONPath expression). That's not something coming out of the blue at all but usually is the same link relation, the client used to find that resource in the first place.
Assume an API root exposing this document:
{
"_links": {
"users": {
"href": "…"
},
…
}
}
Seeing that, the client would have to know the semantics of users to find the link it wants to follow. In your case users is basically pointing to a collection resource that supports pagination.
So if the client follows the link named users, it can then find the actual content it's looking for under _embedded.users in the HAL response by combining knowledge about the media type (HAL, _embedded) and the service's application-level semantics (users).

google-api-javascript-client : How to get contents of file using Drive API?

First off, if there is a question/answer that solves my problem already then I sincerely apologize for creating a new one. However, I have been searching for 3 days now, and have not found an answer...
My problem is, I cannot for the life of me figure out how to pull the contents of a file(any file). From reading the docs I've discovered that my returned file resource object is supposed to have a property named "downloadUrl", and from this I should be able to access the file contents.
None of the file resource objects that are returned to me(via gapi.client.request) have this field/property. Below is the function I am using to get a file.
Can someone please help point me in the right direction? I have to have this demo done by Monday and I've been stuck on this for 2 days....
Here is the code for my get function :
Client.getFileContent = function getFileContent() {
gapi.client.load('drive', 'v2', function() {
var request = gapi.client.request({
path : '/drive/v2/files/1QmaofXyVqnw6ODXHE5KWlUTcWbA9KkLyb-lBdh_FLUs',
method : 'GET',
params : {
projection: "FULL"
}
});
request.execute(function(response) {
console.log(response);
});
});
};
The file resource object that is returned to me does not have the downloadUrl property.
As requested, here is the response object I get back for a text file. Note, I replaced some of the ids with "fileid" for posting here.
"kind": "drive#file",
"id": "fileID",
"etag": "\"-tJAWr_lbRQU2o8gZ0X7BCBIlVk/MTM0MjYyODQ1MTQ2Nw\"",
"selfLink": "https://www.googleapis.com/drive/v2/files/fileID",
"alternateLink": "https://docs.google.com/document/d/fileID/edit",
"embedLink": "https://docs.google.com/document/d/fileID/preview",
"thumbnailLink": "https://docs.google.com/feeds/vt?gd=true&id=fileID&v=1&s=AMedNnoAAAAAUAfLhbYIDsNIn40k7DfRYBsrquijmCii&sz=s220",
"permissionsLink": "https://www.googleapis.com/drive/v2/files/fileID/permissions",
"title": "Copied filed.txt",
"mimeType": "application/vnd.google-apps.document",
"labels": {
"starred": false,
"hidden": false,
"trashed": false,
"restricted": false,
"viewed": true
},
"createdDate": "2012-07-18T16:20:51.132Z",
"modifiedDate": "2012-07-18T16:20:51.467Z",
"modifiedByMeDate": "2012-07-18T16:20:51.467Z",
"lastViewedByMeDate": "2012-07-18T16:20:51.467Z",
"parents": [
{
"kind": "drive#parentReference",
"id": "0AAAYYkwdgVqHUk9PVA",
"selfLink": "https://www.googleapis.com/drive/v2/files/fileID/parents/0AAAYYkwdgVqHUk9PVA",
"parentLink": "https://www.googleapis.com/drive/v2/files/0AAAYYkwdgVqHUk9PVA",
"isRoot": true
}
],
"exportLinks": {
"application/vnd.oasis.opendocument.text": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=odt",
"application/msword": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=doc",
"text/html": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=html",
"application/rtf": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=rtf",
"text/plain": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=txt",
"application/pdf": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=pdf"
},
"userPermission": {
"kind": "drive#permission",
"etag": "\"-tJAWr_lbRQU2o8gZ0X7BCBIlVk/9STkNeCmz61YXorH3hoJimnEgfM\"",
"id": "current",
"role": "owner",
"type": "user"
},
"quotaBytesUsed": "0",
"ownerNames": [
"Joshua.morine"
],
"lastModifyingUserName": "Joshua.morine",
"editable": true,
"writersCanShare": true
}
For native Google documents (Google Spreadsheet, Presentation etc...) we don;t provide a downloadUrl as these can't really be downloaded as files in their native format. Instead you'll have to use one of the URLs in the list of exportLinks which provides URLs to download the Google Documents in a few different export formats.
In your case, a Google Documents the following can be used:
"exportLinks": {
"application/vnd.oasis.opendocument.text": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=odt",
"application/msword": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=doc",
"text/html": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=html",
"application/rtf": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=rtf",
"text/plain": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=txt",
"application/pdf": "https://docs.google.com/feeds/download/documents/export/Export?id=fileID&exportFormat=pdf"
}
The meta-data function you are looking for is actually:
request = gapi.client.drive.files.get({
'fileId': fileId
});
This one produces a result with the downloadUrl that you're referring to. Then it's easy to grab the file using any HTTP request.

Resources