照著AWS文件做就結束了
才不是,如果看完文件,大概很開心的就開始寫自己API想要的POJO,但是別忘了,我們現在的來源是API Gateway的Lambda Proxy integration處理過的內容,他將我們想要的body外包了一層http payload
首先我們來看一下之前log記錄到的reuqest完整內容
{
httpMethod=POST,
body= {
"key1":"value1"
},
resource=/hello,
requestContext= {
resourceId=123456,
apiId=1234567890,
resourcePath=/hello,
httpMethod=POST,
requestId=c6af9ac6-7b61-11e6-9a41-93e8deadbeef,
accountId=123456789012,
stage=Prod,
identity= {
apiKey=null,
userArn=null,
cognitoAuthenticationType=null,
caller=null,
userAgent=Custom User Agent String,
user=null,
cognitoIdentityPoolId=null,
cognitoAuthenticationProvider=null,
sourceIp=127.0.0.1,
accountId=null
},
extendedRequestId=null,
path=/hello
},
queryStringParameters=null,
multiValueQueryStringParameters=null,
headers= {
Cache-Control=no-cache,
Postman-Token=4d185495-a813-47de-8631-c2bcfd27d30c,
Content-Type=text/plain,
User-Agent=PostmanRuntime/7.6.0,
Accept=*/*,
Host=localhost:3000,
Accept-Encoding=gzip,
deflate,
Content-Length=17,
Connection=keep-alive,
X-Forwarded-Proto=http,
X-Forwarded-Port=3000
},
multiValueHeaders= {
Cache-Control= [
no-cache
],
Postman-Token= [
4 d185495-a813-47de-8631-c2bcfd27d30c
],
Content-Type= [
text/plain
],
User-Agent= [
PostmanRuntime/7.6.0
],
Accept= [
*/*
],
Host= [
localhost:3000
],
Accept-Encoding= [
gzip,
deflate
],
Content-Length= [
17
],
Connection= [
keep-alive
],
X-Forwarded-Proto= [
http
],
X-Forwarded-Port= [
3000
]
},
pathParameters=null,
stageVariables=null,
path=/hello,
isBase64Encoded=false
}
我們先試著建立一個RequestBean如下
public class RequestBean {
private String httpMethod;
private Object body;
private Map<String, String> queryStringParameters;
private Map<String, String> headers;
private Map<String, String> pathParameters;
private boolean isBase64Encoded;
public RequestBean() {}
}
你會注意到幾點
然後,按下shift+alt+S -> R ->全選->產生
全部get/set都有了
這時候我們需要一些加工,首先,是headers
headers有甚麼問題了,http的header name定義其實是case-insensitive,而Java的Map預設是case-sensitive。你或許會想,http已經有訂標準的header name不是都是首字母大寫,或反正自訂header name只要明訂哪個字母要大寫,我知道該怎麼大小寫取的到不就好了嗎?
很不幸的,實作上會遇到以下幾個狀況(至少2019九月目前為止的版本)
所以,還是調整一下吧,我個人的方法是全轉小寫再來取值,然後另外新增一個getHeader的方法
public void setHeaders(Map<String, String> headers) {
this.headers = new HashMap<String, String();
headers.forEach((k, v) -> this.headers.put(k.toLowerCase(), v));
}
public String getHeader(String name) {
name = name.toLowerCase();
if (headers.containtsKey(name))
return headers.get(name);
return "";
}
或者你也可以用new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)代替?
重頭戲來啦我的身體呢?
先以簡單的內容來承接測試,並依此撰寫一個Person好了
public class Person {
private String firstName;
private String lastName;
public Person() {}
}
並且一樣記得補上get/set
然後,把RequestBean.body改成Person type就好了嗎?
不行,我們會得到JsonMappingException
Can not instantiate value of type [simple type, class com.amazonaws.lambda.Beans.Person] from String value ('{"firstName": "Spencer", "lastName": "Liu"}'); no single-String constructor/factory method
這樣還需要擁有一個String為參數的建構子,而且要實作deserialize json,而且為了避免每種payload request就要對應一個RequestBean,或許也可以撰寫一個interface或super class等等,可是我覺得這樣還是太麻煩了!
所以撰寫一個getBody的多載,並且用Generic Types來處理如何?
public <T> T getBody(Class<T> bean) {
return this.getBody(bean, null);
}
public <T> T getBody(Class<T> bean, Class<?> view) {
if (body == null) return null;
ObjectMapper mapper = new ObjectMapper();
if (view != null)
mapper.setConfig(mapper.getSerializationConfig().withView(view));
try {
return mapper.readValue(body.toString(), bean);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
於是乎,我們在handler當中想要取得body的內容,只要一行就可以了
Person person = request.getBody(Person.class);
這樣的多載改用了ObjectMapper.readValue,相較於原本的內容,我們可以用完整的Jackson功能,當然也包含annotation註釋的寫法。除此之外也預先準備了view的多載,在之後的文章將會進行應用的示範喔。