My API-first swagger client/server transfers POST request body parameter object incorrectly. Spring (4.3.14.RELEASE) server receives nulls in the #RequestBody parameter of the POST request.
The key difference between requests passed over SwaggerUI and over generated
typescript client is that SwaggerUI passes object fields as query parameters (Spring handles well), but the generated typescript client over json body (Spring handles nulls).
It seems like autogenerated client sends parameter over body, but the autogenerated server expects parameters in the query.
The swagger fragment:
/project:
post:
security:
- Bearer: []
tags:
- Project
summary: Create new project
operationId: createProject
consumes:
- "application/json"
- "text/json"
parameters:
- name: project
in: body
description: project value
schema:
$ref: '#/definitions/ProjectRequestDTO'
responses:
'200':
description: Successful response
schema:
$ref: '#/definitions/ProjectDTO'
default:
description: Bad requst
The resulting Spring MVC method:
ApiOperation(value = "Create new project", nickname = "createHachathon", notes = "", response = ProjectDTO.class, authorizations = {
#Authorization(value = "Bearer")
}, tags={ "Project", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful response", response = ProjectDTO.class),
#ApiResponse(code = 200, message = "Bad requst") })
#RequestMapping(value = "/project",
consumes = { "application/json", "text/json" },
method = RequestMethod.POST)
default ResponseEntity<ProjectDTO> createHachathon(#ApiParam(value = "project value" ) #Valid #RequestBody ProjectRequestDTO project) {
I'm sending request from typescript over manually hacked client (title parameter added as demonstration that this parameter will be unmarshalled well):
return this.httpClient.post<ProjectDTO>(`${this.basePath}/project?title=hello`,
The resulting request in the Chrome console:
Unfortunatelly Spring receives only the value which were passed over query parameter but not over request body:
Plese help me to make my swagger-codegen-maven-plugin 2.3.1 produce client/server swagger API which will transfer data transparently.
The root cause is that Spring ignores method parameter annotations of the implemented Interface, so the #RequestBody was ignored in my Controller implementation.
The <delegatePattern>true</delegatePattern> parameter inside <configOptions> section of swagger-codegen-maven-plugin resolved this problem.
This parameter makes Swagger generate invocation of delegate method inside default implementation of API Interface methods. My Controller implements this method and overrides this delegate mathods, so the source method annotations are in safe.
Sorry, may be late answer. maybe helps someone.
I was hitting same issue while using spring boot+Swagger2.
My REST API with POST method :
public void getAllNames(#ApiParam(name = "Get All names Request",
value = "Request to get all names",
required = true) #RequestBody BasicCredentialsJsonObject requestJson)
I had to introduce Json annotations(JsonPropertyOrder, JsonProperty) on below class to get going. I was hitting issue till I made this changes.
BasicCredentialsJsonObject :
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"password",
"username"
})
public class BasicCredentialsJsonObject {
#JsonProperty("username")
private String username;
#JsonProperty("password")
private String password;
}
Sometimes, it seems stupid but I had issue with my request body, it was null into the controller. What I changed, the import, I was using the swagger.requestBody package instead of the springframeWork.request import.
Related
I'm migrating an application from NancyFx to Kestrel in ASP.NET Core 6.
In Nancy, you could specify the Accept value in the URI. For example, these Uris:
http://localhost:5000/my/resource.json
http://localhost:5000/my/resource.protobuf
http://localhost:5000/my/resource.xml
Would be the equivalent of setting the Accepts header to application/json, application/protobuf or application/xml respectively.
Does this exist in Kestrel? I remember finding one example, long ago, of regex-ing the route and doing it somewhat manually. But
I can't find that post again, and
If I have to do that, I'm not sure I want to :)
Is there a way to configure this behavior in ASP.NET Core 6?
The object returned from my handler in the controller is already capable of being serialized to json/xml/whatever. I just need to check the URI to set the content-type of the response so the correct formatter will be invoked.
At the moment, I have a client that will speak to both Nancy and Kestrel and it was written to use the URI to get the type. I'm fine to rewrite/update the client so it will use the Accept header. But getting the URI method to work will make the initial integration easier and a refactor to use the headers can come next.
I created a very simple middleware that reads the accept value from the query string and sets the Accept header to the request:
public class AcceptHeaderFromQueryString
{
private readonly RequestDelegate _next;
public AcceptHeaderFromQueryString(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var accept = context.Request.Query["accept"];
if (!string.IsNullOrEmpty(accept))
{
context.Request.Headers.Accept = accept;
}
await _next(context);
}
}
Register the middleware:
app.UseMiddleware<AcceptHeaderFromQueryString>();
I added [Produces(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)] attribute to my api controller action (this step is not required):
[HttpGet]
[Produces(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
Finally I added support for xml serialization in Program.cs:
builder.Services.AddControllers()
.AddXmlDataContractSerializerFormatters();
Then I tried these urls and they both gave appropriate response:
https://localhost:7258/weatherforecast?accept=application/json
https://localhost:7258/weatherforecast?accept=application/xml
You possibly want the [Consumes] attribute. This allows you to specify a controller action that only gets called from a route of the specified content type.
Obviously this is not using the Accepts header but the content type of the request.
I have a REST service and I have defined the API signature using OpenAPI's YAML file.
Something like,
title: Sample Pet Store App
description: This is a sample server for a pet store.
termsOfService: http://example.com/terms/
contact:
name: API Support
url: http://www.example.com/support
email: support#example.com
paths:
v1/employees/{employeeId}:
get:
responses:
'200':
content:
....
From the YAML file, I generate the API requests using something like OpenAPI generator.
But how do I specify a https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/Response.html, in my YAML file?
This is how I want to send a response from my Java code. I want to know how I can add this Response object to OpenAPI's YAML?
import javax.ws.rs.core.Response;
#Path("/v1/employees")
public Response getEmployee(String employeeId) {
// ...
return Response
.status(Response.Status.OK)
.entity(employee)
.build();
}
I am new to REST API development. I checked the documentation, but couldn't find details in OpenAPI's on how to add a Javax Response.
Depends on the template you use, by default it is not there, but you can create a custom template to use.
here is the list of available templates.
you must specify type of response you return on OpenAPI spec. like this:
v1/employees/{employeeId}:
get:
operationId: getUser
responses:
200:
description: Return user
content:
application/json:
schema:
$ref: '#/components/schemas/UsertResponseDTO'
after that, if you use default template, add manually typo response like this:
import javax.ws.rs.core.Response;
#Path("/v1/employees")
public Response getEmployee(String employeeId) {
// ...
return Response.ok(employee).build();
}
To solve my problem, instead of returning a Response object, I went with throwing a javax.ws.rs.WebApplicationException and adding an ExceptionTranslator code to convert all my exceptions into a WebApplicationException.
Here is a sample code on the exception translation.
// Actual code to convert Java exception into javax.ws.rs.WebApplicationException.
catch (Exception e) {
throw new WebApplicationException(getResponse(e));
}
// Exception translation code sample. This can be made into a nice generic function to handle different types of exceptions.
Response getResponse(Throwable t) {
if (throwable instanceof NotFoundException) {
Error error = new Error();
error.setType("404");
error.setMessage("Requested entry could not be found");
Response.status(Status.NOT_FOUND)
.entity(error)
.build();
}
}
I'm working with playframework for final project at university and I'm getting a problem when routing a delete or put method.
When I'm requesting a DELETE or PUT methods I'm getting:
[info] play.api.Play - Application started (Dev)
[debug] a.ErrorHandler - onClientError: statusCode = 404, uri = /Rest/deleteCity, message ="
My JQuery ajax call is:
$("#scalaDelete").click(function(){
$("#result").empty();
$.ajax({
url: "http://localhost:9000/Rest/deleteCity",
method: "DELETE",
data: {city: "Alvorada"},
dataType: "json",
success: function(result){
$("#result").append("Result: "+result.Result);
},
error: function (request, status, error) {
alert(status);
}
});
});
My Route Play Route:
DELETE /Rest/deleteCity controllers.RestController.deleteCity()
My Controller Method:
case class UserDelete(city:String)
class RestController #Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {
val userDeleteForm = Form(
mapping(
"city" -> text
)(UserDelete.apply)(UserDelete.unapply)
)
def deleteCity = Action{ implicit request=>
val userPar = userDeleteForm.bindFromRequest.get
//DatabaseDelete
Ok(jsonResult)
}
}
I've already activated cross domain in chrome, I've used a CORS extension for it.
Thanks for helping
This seems related to Restful http delete in play, i.e. DELETE with data can be sketchy.
Instead of passing data, I would just move this to the url:
DELETE /Rest/deleteCity/:city controllers.RestController.deleteCity(city: String)
# or with a query string
DELETE /Rest/deleteCity controllers.RestController.deleteCity(city: String)
and then do
http://localhost:9000/Rest/deleteCity/Alvorada
# or with a query string
http://localhost:9000/Rest/deleteCity?city=Alvorada
Personally I prefer the latter.
I agree with #AndyHayden.
Play ignores the body of the DELETE request, that is the correct behavior to my mind, but you can work around by explicitly passing a body parser:
def delete = Action(parse.json) { implicit request =>
val json = request.body
val someProp = (json \ "someprop").as[String]
Ok(s"Prop is: $someProp")
}
(this example was given by one of the developers of the Play itself:
https://github.com/playframework/playframework/issues/4606#issuecomment-109192802.)
About the doubts in comments:
I've seen another post here where a guy said some browsers just support get and post method.
POST and GET are only valid for the method attribute of the form tag.
You are using javascript request, so you can use any method that server supports. i.e. DELETE is completely fine there.
But something interesting for you to know is that playframework uses akka and this framework does not support DELETE request for security reasons, in fact it wasn't well explained on post. Then if you wanna make a DELETE method you gotta make a post method for complete your code.
Akka HTTP supports the DELETE request (as well as Play Framework): https://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/directives/method-directives/delete.html
I am new to Swagger.
I am using Swagger UI to generate swagger documentation. I have two API calls. First call is to generate token based on user name and password. Second call needs token generated by first call.
How I set that token for second call using Swagger UI?
#ApiImplicitParams and #ApiImplicitParam should do the trick:
#GET
#Produces("application/json")
#ApiImplicitParams({
#ApiImplicitParam(name = "Authorization", value = "Authorization token",
required = true, dataType = "string", paramType = "header") })
public String getUser(#PathParam("username") String userName) {
...
}
From the documentation:
You may wish you describe operation parameters manually. This can be for various reasons, for example:
Using Servlets which don't use JAX-RS annotations.
Wanting to hide a parameter as it is defined and override it with a completely different definition.
Describe a parameter that is used by a filter or another resource prior to reaching the JAX-RS implementation.
The Swagger UI will be updated so you can send your token from there. No changes to HTML will be necessary.
Note: A while ago, when documenting a REST API with Swagger, I realized that just adding #ApiImplicitParam is not enough (even if you have only one parameter). Anyway, you must add #ApiImplicitParams too.
My configuration for 2.9.2 Swagger version to add Authorization on Swagger UI and send the Bearer token
#Configuration
public class SwaggerConfiguration{
//...
#Bean
public Docket api(ServletContext servletContext) {
return new Docket(DocumentationType.SWAGGER_2)...
.securitySchemes(Arrays.asList(apiKey()))
.securityContexts(Collections.singletonList(securityContext()));
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex("/.*")).build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[]{authorizationScope};
return Collections.singletonList(new SecurityReference("Bearer", authorizationScopes));
}
private ApiKey apiKey() {
return new ApiKey("Bearer", "Authorization", "header");
}
}
Another option is to add globalOperationParameters. It will add a field for authorization in every endpoint.
Define authorization header parameter:
Parameter authHeader = new ParameterBuilder()
.parameterType("header")
.name("Authorization")
.modelRef(new ModelRef("string"))
.build();
Add it to Docket configuration:
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(...)
.paths(...)
.build()
.apiInfo(...)
.globalOperationParameters(Collections.singletonList(authHeader));
And it will look like this:
There is a hack that might work by using responseInterceptor and requestInterceptor
First capture response of the the first API call using responseInterceptor and save the token (in the example in local storage), then use requestInterceptor to add the Authorization header with the saved token.
const ui = SwaggerUIBundle({
...
responseInterceptor:
function (response) {
if (response.obj.access_token) {
console.log(response.obj.access_token)
const token = response.obj.access_token;
localStorage.setItem("token", token)
}
return response;
},
requestInterceptor:
function (request) {
console.log('[Swagger] intercept try-it-out request');
request.headers.Authorization = "Bearer " + localStorage.getItem("token");
return request;
}
}
You would have to customise the swagger index page to accomplish that I believe.
You can make the input 'input_apiKey' hidden and add two inputs for username and password. Then you make an ajax call to update the hidden input with your token.
This is an old question but this is how I solved it recently with version 2.7.0 for my JWT tokens
In your swagger configuration, add below SecurityConfiguration bean. Important part being leaving fifth argument empty or null.
#Bean
public SecurityConfiguration securityInfo() {
return new SecurityConfiguration(null, null, null, null, "", ApiKeyVehicle.HEADER,"Authorization","");
}
Add securitySchemes(Lists.newArrayList(apiKey())) to your main Docket bean.
#Bean
public Docket docket()
{
return new Docket(DocumentationType.SWAGGER_2).select()
.....build().apiInfo(...).securitySchemes(Lists.newArrayList(apiKey()));
}
private ApiKey apiKey() {
return new ApiKey("Authorization", "Authorization", "header");
}
Then in UI , you need to click on Authorize button and input "Bearer access_token" (for Authorization text box )where access_token is token provided by jWT token server.
Once this authorization is saved,that will become effective for all end points. Adding a separate text field for each end point looks very cumbersome.
Using SpringDoc with the springdoc-openapi-maven-plugin my option is to use a SwaggerConfig.Java:
#Configuration
public class SwaggerConfiguration {
#Bean
public OpenAPI customOpenAPI(#Value("${project.version}") String appVersion) {
OpenAPI openApi = new OpenAPI();
openApi.info(
new Info()
.title("Title Example")
.version(appVersion)
.description("Swagger server created using springdocs - a library for OpenAPI 3 with spring boot.")
);
openApi.components(
new Components().addSecuritySchemes("bearer-jwt",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
.in(SecurityScheme.In.HEADER).name("Authorization"))
);
openApi.addSecurityItem(
new SecurityRequirement().addList("bearer-jwt", Arrays.asList("read", "write"))
);
return openApi;
}
}
Hi the design document I'm working off of wants me to have a URL in the pattern of
<root>/v1/installs/XYZ123/actions/next?app=1234ABCD HTTP/1.1
However the only examples I can find are extremely simple, and only show URLs that would end at the /next.
http://www.mkyong.com/webservices/jax-rs/jax-rs-queryparam-example/
I'm thinking it's something like... ? I'm missing a key step
#GET
#Produces({ "application/json" })
#Path("v1/installs/{id}/actions<SOMETHINGHERE?>/next HTTP/1.1")
public Response getSetupCommands(#PathParam("id")
String id,#QueryParam("next") String next) {
I'm using jboss and jaxrs
Why do I get the feeling that HTTP/1.1 should not be a part of the URL. You may have read/understood the design document incorrectly. If it was saying that the request should look like
GET /v1/installs/XYZ123/actions/next?app=1234ABCD HTTP/1.1
Then you only need to be worried about /v1/installs/XYZ123/actions/next?app=1234ABCD. HTTP/1.1 is simply the HTTP version that will be used implicitly with every request and response.
Your original example was fine, exception you should have replaced #QueryParam("next") with #QueryParam("app"). next is actually part of the path.
This /v1/installs/{id}/actions/next should be what's included in #Path.
The complete semantics of this request URL seems to read something like:
Get the next (resource) controller, and we will use the app query parameter as an argument to pass to this controller.
UPDATE: with example
#Path("/v1")
public class QueryResource {
#GET
#Path("/installs/{id}/actions/next")
public Response getResponse(#PathParam("id") String id,
#QueryParam("app") String app) {
StringBuilder sb = new StringBuilder();
sb.append("ID: ").append(id).append("<br/>");
sb.append("app param: ").append(app);
return Response.ok(sb.toString()).build();
}
}
Browser Test
Fire bug
v1/installs/{id}/actions/{next : .+}
public Response getSetupCommands(#PathParam("id") String id,#PathParam("next") String next) {
Way too hackish for my tastes, but I don't have any control in this situation..