In Symfony 2.4 I'm using a route and method annotation like follows:
/** di elaborazione creazione offerta
*
* #param Request $request
* #return JsonResponse
*
* #Route("/process", name="process", options={"expose" : true}, defaults={"_format" : "json"})
* #Method("POST")
*/
if I throw a MethodNotAllowedException inside action body, response is correctly a json formatted one whereas a call in HTTP: GET returns a fully formatted html exception page, as if _format attribute would not be loaded.
Is it possible to pass _format attribute to ExceptionController sub-request?
Not allowing #Method("GET") means that Symfony rejects the request at the route level and executes the default exception controller. If you want to override the default exception output, override the default exception behavior as outlined here.
I think you can get a JSON exception simply by adding some .json.twig templates. You could also override the default exception controller if you need more flexibility.
Related
Spring's Kafka producer embeds type header into messages which specifies to which class the message should be deserialized by a consumer.This is a problem when the producer isn't using Spring Kafka, but the consumer is.In that case, JsonDeserializer cannot deserialize a message and will throw an exception "No type information in headers and no default type provided".
One way to get around this is to set a default deserialization type.This won't work in cases where a single topic contains multiple message schemas.
Another solution I've found is to set
spring.kafka.consumer.properties.spring.json.use.type.headers
to false (in application.properties file).This doesn't do anything as the same exception is thrown again.
How do I make sure that JsonDeserializer ignores type headers?
See this option of that deserializer:
/**
* Set to false to ignore type information in headers and use the configured
* target type instead.
* Only applies if the preconfigured type mapper is used.
* Default true.
* #param useTypeHeaders false to ignore type headers.
* #since 2.2.8
*/
public void setUseTypeHeaders(boolean useTypeHeaders) {
It can be configured via property as:
/**
* Kafka config property for using type headers (default true).
* #since 2.2.3
*/
public static final String USE_TYPE_INFO_HEADERS = "spring.json.use.type.headers";
In this case the logic is going to be like this:
this.typeMapper.setTypePrecedence(this.useTypeHeaders ? TypePrecedence.TYPE_ID : TypePrecedence.INFERRED);
which means that the type for deserialization is inferred from the listener method.
See more info in docs: https://docs.spring.io/spring-kafka/reference/html/#json-serde
I have created a Hateoas enabled Rest service using spring-boot-starter-data-rest, works well.
I then created a client of that rest service in another spring boot module: this is a dependency that can be included in other projects that want to use the rest service. It uses a restTemplate under the hood.
It took a bit of mucking around with HttpMessageConverters and TypeConstrainedMappingJackson2HttpMessageConverter to get it to work but it does.
I tried using this dependency in my main application but it failed to populate the links in ResponseEntity< Resource< Myclass> >, leading to null pointer exceptions.
I couldn't track down the problem so I created a basic Spring Boot application 2.1.5.RELEASE and got the client working, then traced back the problem to this configuration in my main application which unfortunately is need for another problem:
spring:
main:
web-application-type: none
If this configuration is present it seems that hal+json isn't the first accepted media type
org.springframework.core.log.CompositeLog.debug(CompositeLog.java:147) : Accept=[application/json, application/hal+json, application/octet-stream, application/*+json]
When the configuration is removed I see
org.springframework.core.log.CompositeLog.debug(CompositeLog.java:147) : Accept=[application/hal+json, application/json, application/octet-stream, application/*+json]
and I can see this logged which fixes the issue I assume ( it isn't logged when the error happens)
- #ConditionalOnProperty (spring.hateoas.use-hal-as-default-json-media-type) matched (OnPropertyCondition)
I have tried adding this configuration to force the issue but it doesn't work
spring:
hateoas:
use-hal-as-default-json-media-type: true
This is my code in the rest client to configure the message converters:
#Configuration
public class MessageConverterConfiguration {
#Bean public TypeConstrainedMappingJackson2HttpMessageConverter myhalJacksonHttpMessageConverter(){
return new TypeConstrainedMappingJackson2HttpMessageConverter( ResourceSupport.class );
}
/**
* Add {#link TypeConstrainedMappingJackson2HttpMessageConverter} to the list of {#link HttpMessageConverter}s
* configured in the {#link RestTemplate} in first position ( this position is critical ).
* #param halJacksonHttpMessageConverter automagically configured by spring-boot-starter-hateoas
* #return List of {#link HttpMessageConverter}s
*/
#Bean( name = "hal-jackson" ) public List< HttpMessageConverter<?> > mymessageConverters( TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter ) {
final List<HttpMessageConverter<?>> all = new ArrayList<>( );
all.add( halJacksonHttpMessageConverter );
all.add( jacksonConverterWithOctetStreamSupport( ) );
all.addAll( new RestTemplate().getMessageConverters() );
return all;
}
/**
* This allows converting octet stream responses into {#link LastApplicationRun} ,
* when we create a last run by posting with {#link RestTemplate#postForObject(URI , Object, Class)}
* : without it we get a
* <pre>org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter
* found for response type [class com.sparknz.ced.spark.sampling.rest.tobesampled.client.domain.LastApplicationRun]
* and content type [application/octet-stream]</pre>.
* <p></p>
* I could find no better solution: it is not needed when we make a get call, don't understand why we get an octet stream response.
* It may only now be useful for tests.
*/
private MappingJackson2HttpMessageConverter jacksonConverterWithOctetStreamSupport( ) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(
asList(new MediaType[]{
MediaType.valueOf( "application/hal+json" ) ,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_OCTET_STREAM }));
return converter;
}
}
What is 'web-application-type: none' doing and how can I get HypermediaHttpMessageConverterConfiguration to run?
I found that adding this to my configuration class did the trick:
#Import( RepositoryRestMvcConfiguration.class )
RepositoryRestMvcConfiguration seems to be responsible for making hal+json the highest priority by adding RepositoryRestMvcConfiguration.ResourceSupportHttpMessageConverter at position 0 in the list of HttpMessageConverters.
We are creating postman collections for all of our end point built on the top of yii2. In all collections, we are using bdd scenarios where we need to test error messages and of course successes. We can compare with the returned error messages to get assertions passed but messages can be changed which eventually make our test fail.
So instead of comparing with string message, I want to compare it with error codes. but i don't know if it is possible to raise errors like exceptions which can be used to test using codeception or endpoints with the tools like postman or swagger.
I have solved this problem. To achieve my requirement i need to extend Yii2 validators where i overwrite "validateAttribute" method. Here i can raise required exception with the code. And this has also allowed me to pass exception type and code directly from the rules.
namespace common\components\validators;
use yii\base\InvalidArgumentException;
class RequiredValidator extends \yii\validators\RequiredValidator
{
public $code;
public $exception;
public function validateAttribute($model, $attribute)
{
parent::validateAttribute($model, $attribute); // TODO: Change the autogenerated stub
if($this->exception){
throw new $exception($this->formatMessage($this->message, ['attribute' => $attribute]),$this->code);
}
}
}
This has allowed me to define rules like following.
public function rules(){
return [
['param', RequiredValidator::className(), 'code' => 100102, 'exception' => 'UserCustomException']
];
}
If the Fat-Free Framework (F3) sees that an incoming HTTP request does not match any of the routes defined in your application, is there a way to set a default route for these cases. For example, to put at the end of all the routes you have defined in the file, a route where any incoming HTTP request that did not match any preceding routes to go there?
Basically, I would like to route any request that doesn't find a match to a specific class/controller. It seems like this would be something that is possible to do, but I cannot find it anywhere in the F3 docs.
Not able to test it but what if you use a wildcard as last route option?
$f3->route('GET /*')
Instead of registering a default route it's better to register a custom error handler which is able to process 404 and other error codes. This approach allows to reuse the error controller or error function when triggering these errors programmatically; e.g. with Base->error(404).
Register the handler with ONERROR
Parse ERROR with the registered ONERROR handler
It's also possible to use the beforeRoute() and afterRoute() events.
Example
<?php
/** #var base $f3 */
$f3->set('ONERROR', 'App\Module\Error\Controller\ErrorController->onError');
class ErrorController
{
public function onError(Base $f3)
{
if ($f3->get('ERROR.code') == 404) {
/**
* TODO Generate an appropriate HTTP 404 page
*/
// Handled the `404` error.
return true;
}
// Let Fat-Free Framework's default error handler do the work.
return false;
}
}
For some reason, I haven't found any normal way to do the following:
I want to Post a json object, and add additional parameters to the call (in this case, an authentication token).
This is a simple RESTful server in myUrl/server, which should give access to different resources of a "person" in the url myUrl/server/person/personCode/resourceName.
GET is easy, and requires no object, only parameters.
The problem arrises when I get to POST - how do I attach the JSON, and keep the other parameters as well?
The class (much has been removed for clarity and proprietary reasons...):
//Handles the person's resources
#Path("/person/{personCode}/{resourceName}")
public class PersonResourceProvider {
#GET
#Produces("application/json")
public String getPersonResource(#PathParam("personCode") String personCode, #PathParam("resourceName") String resourceName, #DefaultValue("") #QueryParam("auth_token") String auth_token) throws UnhandledResourceException, UnauthorizedAccessException {
//Authenticates the user in some way, throwing an exception when needed...
authenticate(personCode, auth_token, resourceName);
//Returns the resource somehow...
}
#POST
#Produces("application/json")
public String postPersonResource(#PathParam("personCode") String personCode, #PathParam("resourceName") String resourceName, #DefaultValue("") #QueryParam("resourceData") String resourceData, #DefaultValue("") #QueryParam("auth_token") String auth_token) throws UnhandledResourceException, UnauthorizedAccessException {
//Again, authenticating
authenticate(personCode, auth_token, resourceName);
//Post the given resource
}
}
Now, the GET method works perfectly, when you go to
myUrl/person/personCode/resourceName, it gives me the correct resource.
The auth_token is used with every single call to the server (for now, authentication is done by comparing with a predefined string), so it's needed. All the other parameters are provided through the path, except for the authentication token, which should not be in the path as it does not relate to the identity of the required resource.
When I get to POST, it's a problem.
I know there's a way to tell the method it consumes a JSON, but in that case, what will happen to the other parameters (auth_token is one of them)?
Should I use Multipart?
Another related question, this is the first time I've designed such a server, is this design correct?
Thanks!
I am not sure I understand what you are trying to achieve. Let me try explain a few things - hope it will be relevant to your question:
#QueryParam injects parameters which are part of your path - i.e. the part of the URL that goes after "?".
E.g. if you have a URL like this:
http://yourserver.com/person/personCode/resourceName?resourceData=abc&token=1234
Then there would be 2 query params - one named resourceData with value "abc" and the other one named token with value "1234".
If you are passing an entity in the POST request, and that entity is of application/json type, you can simply annotate your post method using #Consumes("application/json") annotation and add another parameter to your method, which does not need to be annotated at all.
That parameter can be either a String (in that case Jersey would pass a raw JSON string and you would have to parse it yourself) or it can be a java bean annotated with #XmlRootElement annotation - in that case (if you also include jersey-json module on your classpath) Jersey will try to unmarshall the json string into that object using JAXB. You can also use Jackson or Jettison libraries to do that - see this section of Jersey User Guide for more info: http://jersey.java.net/nonav/documentation/latest/json.html
Found!
Client side:
Client c = Client.create();
WebResource service = c.resource("www.yourserver.com/");
String s = service.path("test/personCode/resourceName")
.queryParam("auth_token", "auth")
.type("text/plain")
.post(String.class, jsonString);
Server side:
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
#Path("/test/{personCode}/{resourceName}")
public class TestResourceProvider {
#POST
#Consumes("text/plain")
#Produces("application/json")
public String postUserResource(String jsonString,
#PathParam("personCode") String personCode,
#PathParam("resourceName") String resourceName,
#QueryParam("auth_token") String auth_token)
throws UnhandledResourceException {
//Do whatever...
}
}
In my case, I will parse the json I get in the server depending on the resource name, but you can also pass the object itself, and make the server consume an "application/json".