I have the following Controller method.
public ActionResult Save(IEnumerable<Model> models)
{
try
{
SaveModels(models);
}
catch (ApplicationException ex)
{
return new HttpStatusCodeResult(500, "error");
}
return new EmptyResult();
}
This will always return "Internal Server Error" as HTTP status description, no matter what message I give to the constructor.
Fiddler output:
HTTP/1.1 500 Internal Server Error
Server: ASP.NET Development Server/10.0.0.0
Date: Tue, 12 Apr 2011 12:44:09 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: private
Content-Length: 0
Connection: Close
If I change the Status Code to 501 I get Not Implemented over the wire, same with 200 OK. And if I select a non-existant status code, like 535 it will just return the status code without any description. I can't see that I'm doing anything wrong according to the documentation and examples I've found from other people using this .
Can anyone see what I'm doing wrong?
I just had the same issue and based on #Mikael's comment, I tried deploying my application to an actual IIS server, and yes, you can specify the actual Status Description and it will be returned to the client side.
Why is it different with Cassini, I'm not really sure about.
Related
I have a Kotlin #RestController, and I would expect it to return 400 Bad Request in a situation where a null is passed for a #RequestParam argument.
Example:
#RestController
class Api() {
#PostMapping("endpoint")
fun endpoint(#DateTimeFormat(iso = DATE) #RequestParam date: LocalDate) {
//do something
}
}
If I were to make a request to POST /endpoint?date I get a 500 Internal Server Error with the following (shortened) body:
{
"timestamp": "2020-09-14T20:39:38.102+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Parameter specified as non-null is null: method Api.endpoint, parameter date",
"trace": "java.lang.IllegalArgumentException: Parameter specified as non-null is null: method Api.endpoint, parameter date\r\n\tat Api.endpoint(Api.kt)\r\n\t
...
...
atjava.base/java.lang.Thread.run(Thread.java:834)\r\n",
"path": "/campaigns/contributions/unfunded/retries"
}
Is there any way to fix this using either some additional library, a configuration, or some custom code that does not have other side effects that keeps everything the same except that the status code will be 400 Bad Request
The following works for Kotlin 1.4.*. This is not necessarily the best answer because there is no guarantee that Kotlin will not change the excption type of message in future releases. For eaxample, when I originally asked the question I was using 1.3.*, and the exception was IllegalArgumentException. In 1.4.* it has been changed to NullPointerException.
#ControllerAdvice
class KotlinNonNullParamHandling {
#ExceptionHandler
protected fun handleKotlinNonNullViolation(
exception: NullPointerException,
response: HttpServletResponse
) {
val nullParameter = exception.message?.startsWith("Parameter specified as non-null is null: ") == true
val restController = exception
.stackTrace
.getOrNull(0)
?.let { Class.forName(it.className) }
?.getAnnotation(RestController::class.java) != null
val status =
if (nullParameter && restController) HttpStatus.BAD_REQUEST
else HttpStatus.INTERNAL_SERVER_ERROR
response.sendError(status.value(), exception.message)
}
}
Before assuming that it is 400, we are checking that the exception
is a NullPointerException
has a message starting with "Parameter specified as non-null is null: "
it was thrown from a class the is RestController (otherwise it might be some other problem, such as a null being sent to a method by reflection having nothing to do with the web layer.)
Spring will throw the IllegalArgumentException by default when a validation fails here. Every exception inside your Spring-Boot application will be treated by default as InternalServerError.
You can modify your response code by adding a function like this:
#RestController
class Api() {
#ExceptionHandler(IllegalArgumentException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void onIllegalArgumentException(IllegalArgumentException e) {
}
#PostMapping("endpoint")
fun endpoint(#DateTimeFormat(iso = DATE) #RequestParam date: LocalDate) {
//do something
}
}
Afterwards you'll get the 400 status code on invalid request param.
I've tested your code, and there's nothing obviously wrong with it - works as expected.
curl -v -d "date=2020-10-01" http://localhost:8080/endpoint
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /endpoint HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Length: 15
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 15 out of 15 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Length: 0
< Date: Tue, 13 Oct 2020 07:04:21 GMT
<
* Connection #0 to host localhost left intact
So I would:
Verify that the client is properly formatting the request - e.g. try explicitly setting the Content-Type header to application/x-www-form-urlencoded, check if there's no typo in the param name etc.
That you have Jackson properly configured to work with Kotlin (com.fasterxml.jackson.module:jackson-module-kotlin - dependency)
I'm unable to perform an HTTP Post with an app running in an Android Emulator.
{StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content:
System.Net.Http.HttpConnection+HttpConnectionResponseContent, Headers:
{ Server: Microsoft-HTTPAPI/2.0 Date: Wed, 23 Oct 2019 00:58:01
GMT Connection: close Forwarded: host=XXX.XXX.X.XX:XXXXX;
proto=https Content-Type: text/html; charset=us-ascii
Content-Length: 374 }}
Setup:
I'm using an IP address generated by Conveyor by Keyoti
I installed a security certificate on the emulator required by Conveyor by Keyoti
I swapped out Microsoft.AspNetCore.Mvc.HttpPost attribute with System.Web.Http.HttpPost
Emulator:
Successful: HTTP Get
Failed: HTTP Post
Integration Test:
Successful: HTTP Post (using same endpoint)
Code:
I wrote an automated test that calls the same HTTP Post implementation.
Because I executed the same code successfully on my laptop via an automated test, I don't think the actual code is the issue:
open Microsoft.AspNetCore.Mvc
open Newtonsoft.Json
[<ApiController>]
[<Route("api/[controller]")>]
type RegisterController () =
inherit ControllerBase()
[<System.Web.Http.HttpPost>]
member x.Post([<FromBody>] json:string) =
...
Summary:
In conclusion, I have isolated the environment to the Android Emulator and not my laptop. Hence, the emulator can successfully trigger an HTTP Get. However, it fails to perform a HTTP Post even though my laptop device can do both.
UPDATE:
I applied guidance from this Xamarin Android ASP.Net Core WebAPI document.
Specifically, I installed another security certificate on the Android emulator.
I was then able to observe an HTTP Get on the Android Emulator.
However, I continue to get an error for HTTP Post.
OperationCanceledException
Physical Device:
If I run the app from a physical android device I observe the following:
{StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.HttpConnection+HttpConnectionResponseContent, Headers:
{
Date: Wed, 23 Oct 2019 13:33:20 GMT
Server: Kestrel
Transfer-Encoding: chunked
Forwarded: host=xxx.xxx.x.xx:xxxxx; proto=https
Content-Type: text/plain
}}
New Update:
I disabled debugging on just my code on the server implementation and discovered the following exception:
Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: 'Bad chunk size data.'
Any suggestions?
this might not be a direct answer to your question, but i would like to suggest
localtunnel. a very easy way to temporarily expose your local api so that you can test it either on emulator or even physical device. Have used this alot my self, as it is very convenient to just type a single line in terminal to start it.
The following reference solved my issue.
Infrastructure:
type GlobalHttpClient private () =
static let mutable (httpClient:System.Net.Http.HttpClient) = null
static member val Instance = httpClient with get,set
Xamarin.Android project:
using Android.Http;
using Android.Net;
using Javax.Net.Ssl;
using System.Net.Http;
using Xamarin.Android.Net;
using Xamarin.Forms;
using WebGatewaySupport;
[assembly: Dependency(typeof(HTTPClientHandlerCreationService_Android))]
namespace Android.Http
{
public class HTTPClientHandlerCreationService_Android : IHTTPClientHandlerCreationService
{
public HttpClientHandler GetInsecureHandler()
{
return new IgnoreSSLClientHandler();
}
}
internal class IgnoreSSLClientHandler : AndroidClientHandler
{
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
{
return SSLCertificateSocketFactory.GetInsecure(1000, null);
}
protected override IHostnameVerifier GetSSLHostnameVerifier(HttpsURLConnection connection)
{
return new IgnoreSSLHostnameVerifier();
}
}
internal class IgnoreSSLHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string hostname, ISSLSession session)
{
return true;
}
}
}
Xamarin.Forms App:
switch (Device.RuntimePlatform)
{
case Device.Android:
GlobalHttpClient.Instance = new HttpClient(DependencyService.Get<IHTTPClientHandlerCreationService>().GetInsecureHandler());
break;
default:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
GlobalHttpClient.Instance = new HttpClient(new HttpClientHandler());
break;
}
Client Gateway:
let postTo (baseAddress:string) (resource:string) (payload:Object) =
GlobalHttpClient.Instance.BaseAddress <- Uri(baseAddress)
let encoded = Uri.EscapeUriString(resource)
let result = GlobalHttpClient.Instance.PostAsJsonAsync(encoded, payload) |> toResult
result
Looks like you have a .NET Core Api. .NET Core does not have System.Web in Asp.NET. The HttpPost attribute and HttpGet attributes should come from Microsoft.AspNetCore.Mvc namespace which you have open.
Also since you are using the ApiController attribute model binding will just work as long as you bind to a model and not just a json string.
Create a model that you want the json to bind to and use that type for your parameter on Post and remove the FromBody attribute. Also if you do that you probably don't need newtonsoft.json.
open Microsoft.AspNetCore.Mvc
[<ApiController>]
[<Route("api/[controller]")>]
type RegisterController () =
inherit ControllerBase()
[<HttpPost>]
member x.Post(thing:TypeOfThing) =
I have a RouterFunction with nested routes in it, everything except one route is doing with I think they're supposed to do.
But when I try to call one of the root routes inside a nested route, I always get a 404. This only happens with that particular route and when I change it from root to "/foo" it starts working.
code:
public RouterFunction<ServerResponse> productRouter(final ProductHandler handler) {
return nest(path(apiPath + BASE_PATH),
route(POST("/").and(contentType(MediaType.APPLICATION_JSON)), handler::handleCreateProduct)
.andRoute(GET("/{id}"), handler::handleGetProductById)
.andRoute(PUT("/").and(contentType(MediaType.APPLICATION_JSON)), handler::handleUpdateProduct)
.andRoute(GET("/"), handler::handleGetAllProducts)
.andNest(path("/category"),
route(POST("/").and(contentType(MediaType.APPLICATION_JSON)), handler::handleCreateProductCategory)
.andRoute(GET("/{id}"), handler::handleGetProductCategoryById)
.andRoute(GET("/"), handler::handleGetAllProductCategories)
.andRoute(GET("/search/{name}"), handler::handleGetProductCategoriesByName)
))
.andNest(path("/brand"),
route(POST("/").and(contentType(MediaType.APPLICATION_JSON)), handler::handleCreateProductBrand)
.andRoute(GET("/"), handler::handleGetAllProductBrands)
.andRoute(GET("/{id}"), handler::handleGetProductBrandById));
}
The route that's not working right is the following:
.andRoute(GET("/"), handler::handleGetAllProductCategories)
The strange thing is under the root path and brands path I do the exact same thing and those routes work.
Thanks for helping out
I didn't manage to reproduce this problem on Spring Boot 2.1.2.RELEASE, with the following:
#Configuration
public class RouterConfig {
#Bean
public RouterFunction<ServerResponse> productRouter() {
return nest(path("/test"),
route(GET("/"), serverRequest -> ServerResponse.ok().syncBody("TEST"))
.andNest(path("/other"),
route(GET("/{id}"), serverRequest -> ServerResponse.ok().syncBody("ID"))
.andRoute(GET("/"), serverRequest -> ServerResponse.ok().syncBody("OTHER"))));
}
}
I'm getting the result:
➜ ~ http :8080/test
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
TEST
➜ ~ http :8080/test/
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
TEST
➜ ~ http :8080/test/other
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
OTHER
➜ ~ http :8080/test/other/
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
OTHER
➜ ~ http :8080/test/other/something
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: text/plain;charset=UTF-8
ID
If you manage to reproduce this problem in a sample application, please create an issue on the Spring Framework issue tracker, as I didn't manage to find an existing issue for that. Please provide a sample project there that we can clone and run to reproduce the problem.
Right now there is an open bug in Weblux about mapping the root element : https://github.com/spring-projects/spring-boot/issues/9785
You should either use redirection or no use the "/" mapping.
Thanks to Brian Clozel's comment I was able to figure it out
keep in mind that router functions are about the first route that matches.
So with that in mind I restructured my RouterFunctions in the following way:
public RouterFunction<ServerResponse> productRouter(final ProductHandler handler) {
return nest(path(apiPath + BASE_PATH),
route(method(HttpMethod.POST).and(accept(MediaType.MULTIPART_FORM_DATA)), handler::handleCreateProduct)
.andNest(path("/category"),
route(GET("/{id}"), handler::handleGetProductCategoryById)
.andRoute(method(HttpMethod.POST).and(contentType(MediaType.APPLICATION_JSON)), handler::handleCreateProductCategory)
.andRoute(GET("/search/{name}"), handler::handleGetProductCategoriesByName)
.andRoute(method(HttpMethod.GET), handler::handleGetAllProductCategories))
.andNest(path("/brand"),
route(method(HttpMethod.POST).and(contentType(MediaType.APPLICATION_JSON)), handler::handleCreateProductBrand)
.andRoute(GET("/{id}"), handler::handleGetProductBrandById)
.andRoute(method(HttpMethod.GET), handler::handleGetAllProductBrands))
.andRoute(GET("/{id}/pdf"), handler::handlaProductDetailsPdf)
.andRoute(GET("/{id}"), handler::handleGetProductById)
.andRoute(method(HttpMethod.PUT).and(contentType(MediaType.APPLICATION_JSON)), handler::handleUpdateProduct)
.andRoute(method(HttpMethod.GET), handler::handleGetAllProducts));
}
I've moved the /category and /brand path both higher in the chain than the root path and it works as expected.
Again thx Brian for the help!
I am developing a react-native application and I am using Couchbase lite as a database locally. The way this works is that you spawn a local REST server when the app starts and you use the REST API to communicate with the CouchbaseLite server.
I have created a few design documents, but when I try to update those I do not get the new results when I run my REST client (seperate app I use for debugging). When I GET the design document it has a new _rev after the update, the new map function is as I updated it, but whenever I do a get on the view the result is the same as the first version of the map function.
Apparently the updated docs are not used by get.
The design doc:
var designDoc = {
name: 'expenses',
language: 'javascript',
views: {
contact_parts_for_group: {
'map': function(doc){
if(doc.type == 'expense'){
emit('some things I emit', doc.amount)
}
}.toString()
}
}
};
I send this to the server along with the proper _rev as the json body: JSON.stringify(designDoc)
.1. I am updating my design document with a PUT call:
PUT /kittydb/_design/expenses?rev=4-6f89f1e13d1fbb89c712d6bab53ee7d4 HTTP/1.1
Host: 127.0.0.1:5800
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.11.2) GCDHTTPRequest
Content-Length: 356
{"name":"expenses","language":"javascript","views":{"contact_parts_for_group":{"map":"function (doc){ if(doc.type=='expense'){ var i,len,part,ref; ref=doc.parts; for(i=0,len=ref.length;i<len;i++){ part=ref[i]; var amount=part.contact==doc.expense_by?-1*part.amount:part.amount; emit([doc.group_id,part.contact,part.contact==doc.expense_by],amount);}}}"}}}
.2. I populate the database using the interface of the app prototype I developed so far
.3. I am not sure what you mean by this.
.4. This is the get:
GET /kittydb/_design/expenses/_view/contact_parts_for_group HTTP/1.1
Host: 127.0.0.1:5800
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.11.2) GCDHTTPRequest
More information in reaction to some comments:
I am using the CouchbaseLite Community Edition, version 1.1.1 for iOS. I am running the simulator as an iPhone 6 with iOS 9.2.
I made some screenshots to illustrate what is going on a bit more:
I don't know how to retrieve the map function that goes with this but what it seems to do is:
emit([doc.group_id,part.contact],amount)
I used the get as above.
Now my update:
PUT /kittydb/_design/expenses?rev=7-6f979706f38acce9c7db380fba8565e4 HTTP/1.1
Host: 127.0.0.1:5800
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.11.2) GCDHTTPRequest
Content-Length: 350
{
"name": "expenses",
"language": "javascript",
"views": {
"contact_parts_for_group": {
"map": "function (doc){ if(doc.type=='expense'){ var i,len,part,ref; ref=doc.parts; for(i=0,len=ref.length;i<len;i++){ part=ref[i]; var amount=part.contact==doc.expense_by?-1*part.amount:part.amount; emit('Hello SO', 'Overflow');}}}"
}
}
}
What it should do now is: emit('Hello SO', 'Overflow');
I get this response when I run the above request:
HTTP/1.1 201 Created
Location: http://127.0.0.1:5800/kittydb/_design/expenses
Content-Type: application/json
Server: CouchbaseLite 1.1 (unofficial)
Etag: "8-3ae4b6ff37b936657ca23acb8d836619"
Accept-Ranges: bytes
Date: Wed, 20 Jan 2016 21:57:03 GMT
Transfer-Encoding: chunked
{"id":"_design\/expenses","rev":"8-3ae4b6ff37b936657ca23acb8d836619","ok":true}
Now I run the get request again:
And nothing changed...
When I create a new document with 'type = expense' I get the same result, just more of them.
I don't know how to retrieve the map function that goes with this
Aha -- if you don't know where the original view definition is, and you can't get it from the design document, it's probably being defined in native code (at app launch time.) Such a definition will override one in a design document.
I don't know anything about React-Native. Is there (as the name implies) native code in the app? If so, look for a call to [CBLView setMapBlock: ...].
i'm hosting an mvc3 web application in a windows azure web-role , and have recently added the html5 cash manifest.
in local environment everything works well , but once uploaded to dev environment on azure , i'm getting an HTTP 500 error when trying to access the manifest.
the cache manifest file is being served by an action and controller , similar to the technique sescribed in Dean Hume's article
the controller:
public ActionResult Manifest()
{
Response.ContentType = "text/cache-manifest";
Response.ContentEncoding = System.Text.Encoding.UTF8;
Response.Cache.SetCacheability( System.Web.HttpCacheability.NoCache);
return View();
}
the View:
#{
Response.ContentType = "text/cache-manifest";
Response.ContentEncoding = System.Text.Encoding.UTF8;
Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
Layout = null;
}
CACHE MANIFEST
# 29/3/2012:V6
NETWORK:
*
CACHE:
#JS FILES
/Scripts/rolllo_1.0.js
/Scripts/rolllo_1.0.js
/Scripts/jquery.mobile-1.0b3.min.js
/Scripts/jquery.validate.min.js
/Scripts/jquery.validate.unobtrusive.min.js
/Scripts/jquery.unobtrusive-ajax.min.js
/Scripts/rolllo_1.0.js
#CSS FILES
/Content/Rtl.css
/Content/JQM/jquery.mobile-1.0b3.css
/Content/Site.css?v=2"
FALLBACK:
/Group/Offline
the _Layout:
<html manifest="#Url.Action("Manifest","Home")">
error messages:
from the chrome console : 'Application Cache Error event: Manifest fetch failed (500)'
and from fiddler :
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/cache-manifest; charset=utf-8
Expires: -1
Date: Thu, 29 Mar 2012 09:32:22 GMT
Content-Length: 5875
i'd love some help.
The problem is probably that IIS is missing the MIME type in Azure. Take a look at:
http://blog.christian-heindel.de/2011/10/23/how-to-host-html5-offline-web-applications-on-an-iis-web-server/
But keep in mind for Azure you will have to do this in a startup task or in the WebRole OnStart method so it will happen anything your instance starts up.