How to handle Github Webhook in Java? - java

Simple question.Got registered Payload URL on Github: using ngrok.com (ngrok) link like explained in Github documentation for Webhooks: Creating Webhooks
ngrok definition: “I want to securely expose a local web server to the internet and capture all traffic for detailed inspection and replay.”
When i send POST request with payload from github on correct Payload URL the response code is 200, how can I handle that request/response and get payload (JSON) in java? With servlet or?
I have no idea where to start. Tried to search but nothing for Java :(
If i put ngrok.com/something, Eclipse console throw:[WARN] 404 - POST /pas (127.0.0.1) 1368 bytes
Request headers
Host: ....ngrok.com
X-Real-IP: 192....
X-Forwarded-Proto: http
Connection: close
Content-Length: 5759
Accept: */*
User-Agent: GitHub-Hookshot/e9dfd89
X-GitHub-Event: ping
X-GitHub-Delivery: c5493000-b67e-11e4-8199-8b09d3d66948
Content-Type: application/json
X-Hub-Signature: sha1=b2947ce6a6de23f4274831523bae375d64e20021
Response headers
Connection: close
Content-Type: text/html;charset=ISO-8859-1
Cache-Control: must-revalidate,no-cache,no-store
Content-Length: 1368
If i put good URL, status is 200. Response on Github Webhooks / Manage webhook:Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 1521
Content-Type: text/html
Date: Tue, 17 Feb 2015 10:17:46 GMT
Last-Modified: Thu, 12 Feb 2015 09:06:18 GMT
Server: nginx/1.6.2
So the question is actually "How to handle that payload?"
In documentation they use Sinatra and that's a big ? for me.
Sinatra code looks like this:
require "sinatra"
require "json"
post "/payload" do
push = JSON.parse(request.body.read)
puts "I got some JSON: #{push.inspect}"
end
New to this, sorry if its stupid question.

Resolved, i used HttpServlet doPost method to fetch request, then from request i getReader() and read line so i can make JSONObject. My servlet is on page/Payload and Webhook is on http://server.com/page/Payload
public class Payload extends HttpServlet {
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder builder = new StringBuilder();
String aux = "";
while ((aux = req.getReader().readLine()) != null) {
builder.append(aux);
}
String text = builder.toString();
try {
JSONObject json = new JSONObject(text);
String teams_url = json.getJSONObject("repository").getString("teams_url");
System.out.println("Teams URL:: "+teams_url);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}

You can make use of gitlab4j-api library. For example usage, have a look at simple review project, exactly here: https://github.com/gitlab4j/simple-cr/blob/master/src/main/java/org/gitlab4j/simplecr/service/GitLabWebHookService.java

Related

Spring Boot: How to resolve Content-Type when incorrectly received from server

I have the following class in Java. I'm expecting it to issue a GET request to the url, get back a JSON payload, and transform that payload to List<LocationData>.
package ...
import ...
#Repository
public class ProxiedLocationRepo {
public List<LocationData> findAll() throws Exception {
RestTemplate restTemplate = new RestTemplate();
String url = UriComponentsBuilder
.fromUriString("https://my-host/path")
.queryParams("some", "queryParams")
.toUriString();
ResponseEntity<List<LocationData>> res = restTemplate.exchange(
url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<LocationData>>(){});
if (res.getStatusCode() == HttpStatus.ACCEPTED) {
return res.getBody();
} else {
throw new ResponseStatusException(res.getStatusCode(), "Did not receive a 200 response from Server.");
}
}
}
However, I'm getting back this error:
org.springframework.http.InvalidMediaTypeException: Invalid mime type "charset=UTF-8": does not contain '/'
Which is expected, because if I do the same request from curl, and check the headers I get this (notice Content-Type line):
$ curl -sfi 'https://my-host/path?some=queryParams'
HTTP/1.1 200 OK
Server: nginx
Date: Wed, 06 Mar 2019 13:58:58 GMT
Content-Type: charset=UTF-8
Content-Length: 1821
Connection: keep-alive
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
... # perfectly formatted JSON payload here
I know that the Content-Type returned from this server is going to be application/json, but it is not providing it to me.
Is there anyway to inform RestTemplate#exchange of what the Content-Type of the response will be? If not, is there any other methodology I could use to resolve this issue besides getting the owners of the server to set the Content-Type correctly?
EDIT:
I have also tried adding the "Accept" header but got the same results:
$ curl -sfi 'https://my-host/path?some=queryParams' \
-H 'Accept: application/json'
Unfortunately I don't think there's any way to fix this while leveraging the Spring framework. Even if you were to create a custom JsonbHttpMessageConverter that accepts a MIME type of ANY, Spring would still fail to parse the incorrect Content-Type received from the request (because it can't find "/" in the Content-Type string).
So the resolution here was to do use java.net.HttpURLConnection to do the networking instead, and then use com.fasterxml.jackson.databind.ObjectMapper to map from the JSON to a POJO.
It works, but at the cost of no longer being able to leverage any of Spring's HTTP handling, which is likely much much more robust than anything I can implement alone.

Response header "Cache-control" visible in PostMan - but not curl -i?

Getting some weird behavior here when using the following spring filter.
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("cache-control", "no-store");
response.addHeader("pragma", "no-cache");
filterChain.doFilter(request, response);
}
It is configured in XML as well, and I know it works, because when I send a request, I am always able to see the "pragma" header in there, no matter what happens.
But the interesting thing here, is that when I hit this from curl - I get the following headers back :
Date: Wed, 19 Apr 2017 20:45:48 GMT
Pragma: no-cache
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html
Transfer-Encoding: chunked
Server: Jetty(9.2.10.v20150310)
But whenever I make the same request via a rest client (such as postman) I see the following headers : Image posted here because I am not allowed to embed images on stackoverflow just yet.
Any idea why this would happen?
EDIT FOR FURTHER INFORMATION : I also see both headers in the network section of developer tools if I hit this from a browser. No idea why it wont appear in curl.
ALSO, I cannot use an interceptor for this as management have decided they don't like them.

REST Response - logging payload via readEntity(String.class) fails

Consider this code for a REST proxy running under Jersey 2.22.2:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Path("/foo")
public Response foo(String request) {
logger.info("Request to /foo :\n" + request);
WebTarget target = ClientBuilder.newClient().target(esbWsUrl).path("/foo");
Response response = target.request(MediaType.APPLICATION_JSON_TYPE).post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
// problem is in the 2 lines below
logger.info("Buffered: " + response.bufferEntity());
logger.info("Response from /foo :\n" + response.readEntity(String.class));
return response;
}
First I tried this without the response.bufferedEntity() and got IllegalStateException since response.readEntity(String.class) consumes from the underlying stream in the Response.
When I use response.bufferEntity(), I am able to call response.readEntity(String.class) multiple times (the buffering seems to work), yet the response returned by Jersey gives a zero bytes response.
$ curl -m 5 -i -X POST -d '{"input_values": "abc"}' http://localhost:9000/services/rest/foo
HTTP/1.1 200 OK
Date: Thu, 27 Oct 2016 08:18:09 GMT
Keep-Alive: timeout=20
X-Type: default
Server: nginx
Content-Type: application/json; charset=UTF-8
Content-Length: 364
curl: (28) Operation timed out after 5000 milliseconds with 0 out of 364 bytes received
Some relevent documentation is here: Response.readEntity
Can anyone please show me how to log the contents of the Response, before returning a valid Response ?
Maybe there is some stream handling issues I havent thought of, or perhaps another way to turn the Response entity to a string (without consuming the stream).
I ended up making a completely new Response, based on the status code and entity of the previous one.

Jersey throws ParseException when uploading file in multipart/form-data request

I am trying to send an mp3 file along with some metadata to my Jersey REST service. For this I am trying to use multipart/form-data content type, however I always get java.text.ParseException: Next event is not a Separator when I send the request. The whole response:
{"restResponse":{
"responseCode":"INVALID_PARAMETER",
"i18nMessage":"Invalid Parameter: Next event is not a Separator",
"responseDescription":"The request contains an invalid parameter"}}
If I remove the mp3 file from the request, it is working properly so I assume there is a problem with how my request is constructed. I am using Paw, which generates request like this:
POST /rest/myapi/recording/multipart HTTP/1.1
Accept: application/json
Accept-Language: en-US
Content-Type: multipart/form-data; boundary=EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Cookie: JSESSIONID=a5f5cfa7329142158766a6182645; JSESSIONIDSSO=BAFA3371F6D14A179B0BA6216DD6C119
Host: localhost:8181
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 44504
--EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Content-Disposition: form-data; name="queue"
qMultipart
--EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Content-Disposition: form-data; name="datetime"
20151029-021807
--EI6FArOacJKf5JCY5BAA2sbl2IAfN8ty
Content-Disposition: form-data; name="recording"; filename="test4_multiform.mp3"
Content-Type: audio/mpeg
ID3ETT2test4_multiformCOMengiTunPGAP0TENiTunes 12.3.0.44COMhengiTunNORM 00000152 00000152 000013BE 000013BE 0000023E 0000023E 00003F10 00003F10 000002A7 000002A7COMengiTunSMPB 00000000 00000210 00000A30 0000000000015BC0 00000000 0
--- the rest of the recording ---
The serverside handler:
#Path(value = "multipart")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public Response receiveFileAndMetadataAsMultipart(
#FormDataParam(RECORDING) InputStream recordingIinputStream,
#FormDataParam(RECORDING) FormDataContentDisposition callRecordingDispositionHeader,
#FormDataParam(QUEUE) String queue,
#FormDataParam(DATETIME) String datetime) throws JSONException, ParseException, IOException {
//do stuff with my recording and metadata
//...
JSONObject response = new JSONObject();
return Response.status(Response.Status.OK).entity(response).build();
}
When I try to send just the file and the content type is not multipart/form-data but application/octet-stream, then it works fine. (I mean not with this handler but one which accepts octet-stream) although the request is very similar. I'll post the request and code for octet stream as well, maybe it helps:
PUT /rest/myapi/recording HTTP/1.1
Accept: application/json
Accept-Language: en-US
Content-Type: application/octet-stream
Cookie: JSESSIONID=a5f5cfa7329142158766a6182645; JSESSIONIDSSO=BAFA3371F6D14A179B0BA6216DD6C119
Host: localhost:8181
Connection: close
User-Agent: Paw/2.2.2 (Macintosh; OS X/10.10.5) GCDHTTPRequest
Content-Length: 55414
ID3BTT2test3_2callsCOMengiTunPGAP0TENiTunes 12.3.0.44COMhengiTunNORM 0000038A 0000038A 00001B3D 00001B3D
--- the rest of the recording ---
Method:
#Path(value = "recording")
#PUT
#Consumes(MediaType.APPLICATION_OCTET_STREAM)
#Produces(MediaType.APPLICATION_JSON)
public Response receiveRecording(InputStream callRecordingInputStream) throws IOException, JSONException {
//do stuff with my recording
//...
JSONObject response = new JSONObject();
return Response.status(Response.Status.OK).entity(response).build();
}
Does anyone have any idea what is wrong? I found this approach with multipart on several places online so I guess it should work doing it like that.
Btw, I also tried removing all of the metadata and just send the recording but the problem was the same.
I have found the issue thanks to #peeskillet's comment, so just in case this exact same thing happens to someone else:
I had a bad import for FormDataContentDisposition.
Wrong:
import com.sun.jersey.core.header.FormDataContentDisposition;
Correct:
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;

Custom Response + HTTP status?

I have a rest interface for my project.
For one class i have a POST method where you can post an xml and i RETURN a custom response like:
<customResponse>Invalid email</customResponse>
if the email from the xml which was posted, was incorrect + other custom messages i have defined for different situations.
For all of these the HTTP STATUS is automatically put on 200 (OK).
Is there any way to change it?
Ps: I know that i can throw a web application like :
throw new WebApplicationException(Response.Status.BAD_REQUEST);
but in this case my custom response is no more included.
So i just want to return my custom error + 400 as http response.
Thanks in advance.
UPDATE after comments:
My method is:
#POST
#Path("{membershipExternalId}")
#Consumes(MediaType.APPLICATION_XML)
#Produces("application/xml")
public CustomResponse invite(){ //code}
You see that i return my CUSTOM RESPONSE. If i would return simple RESPONSE i could set the STATUS but in this case i cannot see any way.
Found the solution:
Put the return type as Response to the method:
#POST
#Path("{membershipExternalId}")
#Consumes(MediaType.APPLICATION_XML)
#Produces("application/xml")
public Response invite(){ //code
if (fail())
return Response.status(400).entity(customResponse).build();
}
Response.status(400).entity(customResponse) will do the trick. When build() it will convert your custom response xml =>
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
X-Powered-By: Servlet 2.4; JBoss-4.2.3.GA (build: SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
Set-Cookie: JSESSIONID=1C72921619A6B32BC1166B3567A39ADA; Path=/
Content-Type: application/xml
Content-Length: 140
Date: Thu, 18 Mar 2010 12:15:15 GMT
Connection: close
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><customResponse><message>Invalid email</message></customResponse>
setStatus or sendError on HttpServletResponse should do the trick.
This is tagged Java but I don't recognize Response.Status.BAD_REQUEST.
For Java just call setStatus on the HttpServletResponse object.
For .NET it looks like this:
HttpContext.Current.Response.StatusCode = xxx;

Categories