Query string parameters is shown as null although value is entered - aws-lambda

I'm developing a AWS Lambda function which will be invoked from a API gateway. The Lambda function and the API gateway are setup and the user is supposed to enter the search string in query string parameters like: http://apigatewayurl?name=some name. Then the application has the logic to return the respective id.
The class declaration is as follows:
public class PayRollIdExtraction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {}
The handleRequest method is here:
public APIGatewayProxyResponseEvent handleRequest( final APIGatewayProxyRequestEvent input, final Context context)
{
System.out.println("Enter handleRequest...");
Map<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json");
headers.put("Content-Type", "application/x-www-form-urlencoded");
headers.put("Access-Control-Allow-Origin", "*");
headers.put("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
headers.put("Access-Control-Allow-Headers", "origin, content-type, accept, x-requested-with");
headers.put("Access-Control-Max-Age", "3600");
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent().withHeaders(headers);
String bodyText="";
if(input!=null) {
bodyText+="The value of input is: "+input;
Map<String, String> params= input.getQueryStringParameters();
if(params!=null) {
String enteredName=params.get("name");
PayRollIdExtraction px=new PayRollIdExtraction();
int output=px.execute(enteredName);
return response.withStatusCode(200).
withBody("The person id of "+enteredName+ " is: "+Integer.toString(output));
}else {
return response.withStatusCode(500)
.withBody(bodyText+" And The name paramter is null");
}
}else {
return response.withStatusCode(500)
.withBody("The input can't be null");
}
}
When I'm entering correct names as query string parameter like below:
http://apigatewayurl/?name=Timothy Hull
I'm getting the following response:
"statusCode":500,"headers":{"Accept":"application/json","Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Max-Age":"3600","Access-Control-Allow-Headers":"origin, content-type, accept, x-requested-with","Content-Type":"application/x-www-form-urlencoded"},"body":"The value of input is: {} And The name paramter is null"}
I'm clueless as why the input is printed as empty {} as I have clearly entered the correct available name? Can anyone please guide me? Let me know if any information is missing?

Related

the passed param by resttemplate.exchange was not decoded in service side automatically

Below is my REST API code:
#RequestMapping(value = "/get", method = RequestMethod.POST, produces = { "application/json" })
#ApiOperation(value = "get data by key.", notes = "return json string value.")
public JsonObjectResponse<String> get(
#ApiParam(required = true, name = "regionName", value = "region name") #RequestParam("regionName") String regionName,
#ApiParam(required = true, name = "key", value = "region key,Default is uuid") #RequestParam("key") String key) throws UnsupportedEncodingException
{
JsonObjectResponse<String> jr = new JsonObjectResponse<String>();
//key = decodeJsonString(key); // added for junit
String val = adfService.onPath(regionName).get(key);
jr.setState(StateCode.SUCCESS);
jr.setData(JsonObject.create().append(key,val).toJson());
return jr;
}
I'm trying to pass parameters:
regionName=/fusion/table1&key={"fusionTbl1DetailNo":"fusionNo001","pk":"PK0001"}
If I call it via swagger-ui, it calls like this:
http://localhost:8080/service/basic/get?regionName=%2Ffusion%2Ftable1&key=%7B%22fusionTbl1DetailNo%22%3A%22fusionNo001%22%2C%22pk%22%3A%22PK0001%22%7D&token=8652493a-4147-43f4-af3a-bcb117fb7d42`
It encoded the parameters and these parameters also can be decoded automatically in server side correctly.
When I want to add testcase for this API, I use restTemplate.exchange method, code as below:
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
for (Entry<String, String> entry : queryParamMap.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
if (uriParamMap != null) {
url = builder.buildAndExpand(uriParamMap).toUriString();
} else {
url = builder.toUriString();
}
if (StringUtils.isEmpty(requestBody)) {
if (bodyParamMap != null) {
requestBody = parseMapToParams(bodyParamMap);
} else {
requestBody = "";
}
}
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
// headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
// headers.add("Accept", "application/json");
// headers.set(HttpHeaders.ACCEPT, "application/json");
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
// headers.add("Accept-Encoding", "gzip, deflate, br");
headers.set("Accept-Charset", "utf-8");
headers.set("Accept-Encoding", "gzip");
headers.add("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6");
headers.add("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
headers.add(TestBase.TOKEN_HEADER, TestBase.getTokenId());
HttpEntity<String> request = new HttpEntity<>(body, headers);
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
ResponseEntity<String> response = restTemplate.exchange(url, httpMethod, request, String.class);
localresponse.set(response);
System.out.println("response:" + response);
return response;
I used UriComponentsBuilder to append the parameters, it will format the url to
http://localhost:8080/service/basic/get?regionName=/fusion/table1&key=%7B%22fusionTbl1DetailNo%22:%22fusionNo001%22,%22pk%22:%22PK0001%22%7D
for method exchange. However, when the server side received the call, it did not decode the param key, it's value still was:
%7B%22fusionTbl1DetailNo%22:%22fusionNo001%22,%22pk%22:%22PK0001%22%7D
Why is that? I compared the header settings from the swagger calling, added additional settings, no effect :(.
Instead of toUriString() use, UriComponentsBuilder.fromUriString(url).queryParam("name","John Doe").build().toString();
Try like the following:
ResponseEntity<String> res = restTemplate.exchange("http://localhost:8080/service/basic/get?regionName={arg1}&key={arg2}", HttpMethod.POST, null, String.class,"/fusion/table1", "{\"fusionTbl1DetailNo\":\"fusionNo001\",\"pk\":\"PK0001\"}");
arg1 and arg2 will be replaced by
"/fusion/table1" and "{\"fusionTbl1DetailNo\":\"fusionNo001\",\"pk\":\"PK0001\"}"
I send null in requestEntity as no request body and request parameters is in uriVariables.
The Spring Documentation on RestTemplate reads:
For each HTTP method there are three variants: two accept a URI
template string and URI variables (array or map) while a third accepts
a URI. Note that for URI templates it is assumed encoding is
necessary, e.g. restTemplate.getForObject("http://example.com/hotel
list") becomes "http://example.com/hotel%20list". This also means if
the URI template or URI variables are already encoded, double encoding
will occur,
It looks like you are using a RestTemplate exchange method that takes a URI template string and you should therefore NOT encode the url-string.
The url-string is first encoded on
builder.toUriString()
And then again on the exchange-call. So the problem seems to be double encoding on the client side, not lack of decoding on the server side

Enabling WebAPI CORS for Angular 2 authentification

I've seen a few answers on stackoverflow and I'm lost.
I have webapi 2 + standalone angular 2
webapi project is from template. the only thing i've changed is that i added CORS
and following line to IdentityConfig.cs > ApplicationUserManager Create()
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "http://localhost:3000" });
here I've all standard from template:
[Authorize]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
On the client side I have function to get access token, that works properly:
authenticate(loginInfo: Login): boolean {
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
this.http.post(this.baseUrl + 'Token', 'grant_type=password&username=alice2#example.com&password=Password2!',
{
headers: headers
})
.subscribe(
data => this.saveAuthToken(<AccessToken>(data.json())),
err => this.handleError(err),
() => console.log('authentication Complete')
);
return true;
}
And get function, that works ok without authentication (commented code) :
get(url: string) {
var jwt = sessionStorage.getItem(this.idTokenName);
var authHeader = new Headers();
if (jwt) {
authHeader.append('Authorization', 'Bearer ' + jwt);
}
return this.http.get(this.apiUrl + url, {
headers: authHeader
})
.map(res => res.json())
.catch(this.handleError);
//return this.http.get(this.apiUrl + url)
// .map(res => res.json())
// .catch(this.handleError);
}
But when i try to add Authorization header server returns:
XMLHttpRequest cannot load http://localhost:3868/api/values. Response for preflight has invalid HTTP status code 405
How to allow user to authenticate through Angular properly?
Install-Package Microsoft.Owin.Cors
Add to App_Start > Startup.Auth.cs > ConfigureAuth(IAppBuilder app)
app.UseCors(CorsOptions.AllowAll);
Only one line. That's all.
You could explicitly add the needed headers and methods:
context.Response.Headers.Add(
"Access-Control-Allow-Headers",
new[] { "Content-Type, Authorization" }
);
context.Response.Headers.Add(
"Access-Control-Allow-Methods",
new[] { "GET, POST, OPTIONS" }
);
I had to add the following to the globalasax.cs:
protected void Application_BeginRequest()
{
var req = HttpContext.Current.Request;
var res = HttpContext.Current.Response;
var val = res.Headers.GetValues("Access-Control-Allow-Origin");
if (val == null)
{
if (!req.Url.ToString().ToLower().Contains("token") || (req.Url.ToString().ToLower().Contains("token") && req.HttpMethod == "OPTIONS"))
{
res.AppendHeader("Access-Control-Allow-Origin", "http://localhost:4200");
}
}
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
res.StatusCode = 200;
res.End();
}
}
When talking to webapi angular and using a http post that either contains non-standard body contents (i.e json) or authentication then a pre-flight request is set that basically says 'am i okay to send the actual request'. Now there are several ways around this that essentially involve short cuts - use IE (if the server is on the same machine as IE ignores the port when deciding what the same machine is) or open CORS up to permit all (which is dangerous as the granting permission to an authenticated user opens your system up to all manner of hacks).
Anyway the solution we used was to add a method to the Globals.asax.cs on the server
protected void Application_BeginRequest()
{
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
var origin = HttpContext.Current.Request.Headers["Origin"];
Response.Headers.Add("Access-Control-Allow-Origin", origin);
Response.Headers.Add("Access-Control-Allow-Headers", "content-type, withcredentials, Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
Response.Headers.Add("Access-Control-Allow-Credentials", "true");
Response.Headers.Add("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS, POST, PUT, DELETE");
Response.Flush();
}
}
Now the above is checking for the pre-flight very specifically and if it finds it it adds permissions to send the next request. On your system you may need to tweek the Allow_Headers request (easiest way is to use your browser f12 to look at what headers your pre-flight request is actually sending out.
Note that the above just deals with the pre-flight CORS will still apply for the actual http POST which will need correctly handling. For this we added the server we wanted to allow in to settings and then added the System.Web.Http.Cors to the WebApiConfig Register method as follows
var cors = new EnableCorsAttribute(Properties.Settings.Default.CORSOriginPermittedSite, "*", "GET, HEAD, OPTIONS, POST, PUT, DELETE");
cors.SupportsCredentials = true;
config.EnableCors(cors);
This avoids hard coding the site which a production system really wants to avoid.
Anyway hopefully that will help.

WebAPI OData v4 custom action without parameters can't be routed with error "No routing convention was found..."

I have very simple OData controller that successfully process standard actions (at least GET, POST, PUT and DELETE methods are working). I have followed this tutorial and added simple bound action. The method has parameters argument, but actually it does not required the parameters:
[HttpPost]
public IHttpActionResult Close([FromODataUri] int key, ODataActionParameters parameters) {
return Ok();
}
I have defined this action in OData EDM configuration as following:
builder.EntitySet<Ticket>("tickets");
builder.EntityType<Ticket>().Action("Close");
I am trying to call action from Postman:
POST /odata/tickets(2)/Default.Close HTTP/1.1
Host: localhost:50477
Accept: application/json
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: eef4c1f6-8c7f-f5eb-c22d-4397f3bda170
But receives the error message:
{
"error": {
"code": "",
"message": "No HTTP resource was found that matches the request URI 'http://localhost:50477/odata/tickets(2)/default.close'.",
"innererror": {
"message": "No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.",
"type": "",
"stacktrace": ""
}
}
}
I have read the whole internet and all related articles on SO but can't fix this issue. Please help me because I have no any fresh idea how to fight this.
My controller:
public class TicketsController : ODataController
{
[HttpPost]
public IHttpActionResult Close([FromODataUri] int key, ODataActionParameters parameters)
{
return Ok();
}
}
My request:
string requestUri = "http://localhost/odata/tickets(2)/Default.Close";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Content = new StringContent("",
Encoding.UTF8,
"application/json");
HttpResponseMessage response = _client.SendAsync(request).Result;
Or remove the ODataActionParameters parameters in the close method and call with:
string requestUri = "http://localhost/odata/tickets(2)/Default.Close";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
HttpResponseMessage response = _client.SendAsync(request).Result;
My EdmModel is use your model.

405 Method Not Allowed on /api/login OPTIONS request with grails-spring-security-rest plugin (and the fight continues...)

In my app, I am using grails-spring-security-rest plugin and I am currently at the stage of building authentication flow.
If I use a rest client everything works as expected: I am able to login by posting username & password in json and get tokens back. Perfect!
Now, I am trying to integrate this whole thing with the web form and, of course, the browser sends preflight OPTIONS request.
I have a simple interceptor setup:
#GrailsCompileStatic
class CorsInterceptor {
int order = HIGHEST_PRECEDENCE
CorsInterceptor() {
matchAll() // match all controllers
//.excludes(controller:"login") // uncomment to add exclusion
}
boolean before() {
String origin = request.getHeader("Origin");
boolean options = "OPTIONS".equals(request.getMethod());
if (options) {
if (origin == null) return;
response.addHeader("Access-Control-Allow-Headers", "origin, authorization, accept, content-type, x-requested-with");
response.addHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS");
response.addHeader("Access-Control-Max-Age", "3600");
}
response.addHeader("Access-Control-Allow-Origin", origin == null ? "*" : origin);
response.addHeader("Access-Control-Allow-Credentials", "true");
true // proceed to controller
}
boolean after() { true }
void afterView() {
// no-op
}
}
The interceptor works perfectly got valid get requests and adds the headers into the response. However, when I am trying to senf this:
curl -X "OPTIONS" "http://localhost:8080/api/login" \
-H "Origin: http://localhost:3000" \
-H "Content-Type: application/json" \
-d "{\"username\":\"customer\",\"password\":\"password\"}"
I am always getting 405 Method Not Allowed back and the execution is not even getting to interceptor at all.
My assumption is that the login controller provided by the plugin is not allowing that, and I need to put an additional URL mapping to overcome this problem. My problem is, what this mapping support to look like?
Also, it is possible to setup mapping that will work for all OPTIONS requests, so I don' need to specify them one by one?
Given all that, it is only my assumption... Am I even in the right direction with it?
Thanks,
this problem was faced by many other users as well and was repeatedly asked on github and slack channels. I've created a sample example which has CORS filter in under src/ directory and I've registered it as spring bean. Here is github repo with example app. The Cors filter code is below
#Priority(Integer.MIN_VALUE)
class CorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws ServletException, IOException {
String origin = req.getHeader("Origin");
boolean options = "OPTIONS".equals(req.getMethod());
if (options) {
if (origin == null) return;
resp.addHeader("Access-Control-Allow-Headers", "origin, authorization, accept, content-type, x-requested-with");
resp.addHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS");
resp.addHeader("Access-Control-Max-Age", "3600");
}
resp.addHeader("Access-Control-Allow-Origin", origin == null ? "*" : origin);
resp.addHeader("Access-Control-Allow-Credentials", "true");
if (!options) chain.doFilter(req, resp);
}
}
Register this as spring bean in resources.groovy file as below:
beans = {
corsFilter(CorsFilter)
}
Here is the question asked on github repo of this plugin.
Update
Grails3 CORS interceptor plugin has been update to include SpringSecurityCorsFilter. For details of how to use, refer to this sample
This plugin is much better than the servlet filter I've written above.
Shurik, the 'filters' you speak of are now called 'interceptors' to match more common nomenclature. This 'filter' is NOT deprecated as this is an 'ACTUAL' filter in Grails/SpringBoot. This is why they deprecated the Interceptors from being called this because of all the naming confusion.

Oracle MAF-MCS API call

I have created a custom POST api for getting login information in MCS. when i check in SOAPUI it works perfectly fine. the parameters passed are
1. header
Oracle-Mobile-Backend-Id: ********************
2. Authentocation
Username:****************
password: **************
and basic login info username and password as "User1" and "user1" respectively.
Step2:
when i call the API from MAF i am getting an error 400
the post method used is
public static Response callPost(String restURI, String jsonRequest) {
String responseJson = "";
Response response = new Response();
RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
restServiceAdapter.clearRequestProperties();
//restServiceAdapter.setConnectionName("MiddlewareAPI");
// restServiceAdapter.setConnectionName("");
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_POST);
restServiceAdapter.addRequestProperty("Content-Type", "application/json");
restServiceAdapter.addRequestProperty("Accept", "application/json; charset=UTF-8");
restServiceAdapter.addRequestProperty("Oracle-Mobile-Backend-Id", "**********");
restServiceAdapter.addRequestProperty("Domain", "mcsdem0001");
restServiceAdapter.addRequestProperty("Username", "******");
restServiceAdapter.addRequestProperty("Password", "*****");
//restServiceAdapter.addRequestProperty("Authorization", "Basic "+new String(encodedBytes));
System.out.println("**** Authorization String ****=>"+new String(encodedBytes));
System.out.println("**** RestURI ******=>"+restURI);
System.out.println("**** jsonRequest ******=>"+jsonRequest);
restServiceAdapter.setRequestURI(restURI);
restServiceAdapter.setRetryLimit(0);
try {
responseJson = restServiceAdapter.send(jsonRequest);
int responseCode = restServiceAdapter.getResponseStatus();
response.setResponseCode(responseCode);
response.setResponseMessage(responseJson);
response.setHeader(restServiceAdapter.getResponseHeaders());
} catch (Exception e) {
int responseCode = restServiceAdapter.getResponseStatus();
response.setResponseCode(responseCode);
response.setResponseMessage(responseJson);
}
System.out.println("Response:" + responseJson);
return response;
}
Could anyone please tell me is there any error in the post method??
This can be due to the version conflict. Try to use HttpUrlConnection instead of RestServiceAdapter and let me know if it works.
actually this bit
restServiceAdapter.addRequestProperty("Username", "******");
restServiceAdapter.addRequestProperty("Password", "*****");
doesn't work because you attempt to pass username and password as a HTTP header. Instead it should be passed as you were trying here
restServiceAdapter.addRequestProperty("Authorization", "Basic "+new String(encodedBytes));
However, these should not be encoded bytes but a base64 encoded string in the form
Basis (without the < abd >)
Note that user identity domains only need to be provided in multi-tenant environments. In MCS, the user domain is defined through the mobile backend you connect to.
Frank
Use the MAF MCS Utility library to make it allot easier.
The developer guide can be found here: http://download.oracle.com/otn_hosted_doc/maf/mafmcsutility-api-doc-082015.pdf
Example code:
MBEConfiguration mbeConfiguration =
new MBEConfiguration(
<mbe rest connection>,<mobileBackendId>,
<anonymous key string>,<application key string>,
MBEConfiguration.AuthenticationType.BASIC_AUTH);
mbeConfiguration.setEnableAnalytics(true);
mbeConfiguration.setLoggingEnabled(false)
mbeConfiguration.setMobileDeviceId(
DeviceManagerFactory.getDeviceManager().getName());
MBE mobileBackend = MBEManager.getManager().
createOrRenewMobileBackend(<mobile backend Id>, mbeConfiguration);
CustomAPI customApiProxy = mbe.getServiceProxyCustomApi();
MCSRequest request = new MCSRequest(mobileBackend.getMbeConfiguration());
request.setConnectionName(<Rest connection name>);
request.setRequestURI("/moile/custom/mockup/employees");
request.setHttpMethod(MCSRequest.HttpMethod.POST);
request.setPayload("{\"id\":\"1\"\"name\":\"nimphius\",\"firstName\":\"frank\"}");
request.setRetryLimit(0);
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type","application/json");
request.setHttpHeaders(headers);
MCSResponse response = customApiProxy .sendForStringResponse(request);
String jsonResponse = (String) response.getMessage();

Resources