5

I have an app to create server certificate requests, just as if one were using java keytool or something. I'm trying to return the created certificate request and the key in a zip file, but for the life of me, I can't get my REST controller to respond to the http request. CORRECTION: The controller responds, but the code within the method is never executed.

The server does receive the request, because my CORS filter is executed. But I have a debug set in the controller method, and it's never triggered. Is the signature of the method correct? I need another set of eyes, please?

Here is my controller code:

@RequestMapping(method = RequestMethod.POST, value = "/generateCert/")
public ResponseEntity<InputStreamResource> generateCert(@RequestBody CertInfo certInfo) {
    System.out.println("Received request to generate CSR...");

    byte[] responseBytes = commonDataService.generateCsr(certInfo);
    InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(responseBytes));

    System.out.println("Generated CSR with length of " + responseBytes.length);
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=certificate.zip")
            .contentType(MediaType.parseMediaType("application/zip"))
            .contentLength(responseBytes.length)
            .body(resource);
}

And here is the Angular request:

generateCertificate(reqBody: GenerateCert) {
   let headers = new Headers();
   headers.append('Content-Type', 'application/json');

   this.http.post(this.urlGenerateCert, JSON.stringify(reqBody), {headers: headers}).subscribe(
    (data) => {
        let dataType = data.type;
        let binaryData = [];
        binaryData.push(data);
        this.certBlob = new Blob(binaryData);
    });
    return this.certBlob;
 }

And finally, the request and response headers I copied from the Network Panel:

Response
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, X-Requested-With, remember-me
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 3600
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Date: Thu, 27 Dec 2018 22:48:00 GMT
Expires: 0
Location: http://localhost:8102/login
Pragma: no-cache
Set-Cookie: JSESSIONID=EDACE17328628D579670AD0FB53A6F35; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

Request
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 205
Content-Type: application/json
Host: localhost:8102
Origin: http://localhost:4200
Referer: http://localhost:4200/generateCerts
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36

I really struggled with getting CORS working, so maybe that's interfering with the request? I hate to post all that code unless absolutely necessary. Anybody got any ideas?

CNDyson
  • 1,687
  • 7
  • 28
  • 63
  • 2
    Here we go again. http.post returns an Observable. Not a Blob. Why? Because AJAX is asynchronous. So it returns an observable, that allows you to be notifid, much later, when the response is finally available. When you execute `this.certBlob`, you do it immediately after you've sent the request. The response has not come back yet. So this.certBlob is undefined. A service must return an observable. The component interested in the response must subscribe to the returned observable, to be notified when the certificate has come back. – JB Nizet Dec 27 '18 at 23:07
  • Umm, I believe I do subscribe to the request - scroll over. – CNDyson Dec 27 '18 at 23:12
  • You do subscribe. The, right after you've subscribed, and thus sent the request, you return this.certBlob, which is undefied, since it will only be populated much later, after the method has returned for a long time, when the callback passed to subscribe has been called because the response has finally comes back from the server. You can't eat a toast immediately after you've put it in the toaster. You need to wait until the toaster tells you that the toast s grilled. – JB Nizet Dec 27 '18 at 23:15
  • Would you mind sharing what I should be doing instead? UI development isn't my specialty. – CNDyson Dec 27 '18 at 23:16
  • But keep in mind, my problem is that the backend isn't responding at all. I haven't even gotten to validating the file yet. – CNDyson Dec 27 '18 at 23:17
  • 1
    I told you already. Read my first comment. The service must return an abservable. I.e. `return this.http.post(...);` The component which calls te service and wants to access the certificate must subscribe to the observable returned by the service. Do NOT subscribe in the service. – JB Nizet Dec 27 '18 at 23:18
  • If the server isn't responding at all, how come you have 2 responses listed in your question? – JB Nizet Dec 27 '18 at 23:20
  • I should have been more specific - my controller method isn't invoked. – CNDyson Dec 27 '18 at 23:21
  • Then what happens precisely, and what are those responses in your question then? Are they even relevant? – JB Nizet Dec 27 '18 at 23:23
  • The servlet filter is called, but the request is never mapped to the controller method. I thought maybe something was wrong with the signature? And I misstyped - those are the request & response I receive. I thought something may be gleaned from them. – CNDyson Dec 27 '18 at 23:26
  • Start by opening the network panel in your browser dev tools? What are the requests being sent? If you're using CORS, you should hve an OPTIONS followed by a POST. Check that the POST is there. Check the URL. (Why are you using CORS, BTW, do you really plan to open this API to external JavaScript applications?) – JB Nizet Dec 27 '18 at 23:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/185855/discussion-between-user1660256-and-jb-nizet). – CNDyson Dec 27 '18 at 23:28
  • No, I don't chat. Please clarify your question. – JB Nizet Dec 27 '18 at 23:28
  • Question edited! – CNDyson Dec 27 '18 at 23:35
  • You still haven't said if a POST request was sent or not, and what the URL of the request was. If a POST request is not sent, then it means your CORS configuration isn't right. And you still haven't said why you're using CORS either. – JB Nizet Dec 27 '18 at 23:38
  • Did you annotate your back end `generateCert` method with `@CrossOrigin` ? – Abder KRIMA Dec 28 '18 at 00:09
  • I annotated the class, yes. – CNDyson Dec 28 '18 at 00:32
  • 3
    Why not use a REST Client e.g. Postman to test the endpoint first. This will help to know exactly if the problem is with the angular app or the spring-boot app. – Olantobi Jan 02 '19 at 20:50

4 Answers4

1

When Angular encounters this statement

this.http.post(url,body).subscribe(data => # some code
);

It comes back immediately to run rest of the code while service continues to execute. Just like Future in Java.

Here if you

return this.cert;

You will not get the value that may eventually get populated by the this.http service. Since the page has already rendered and the code executed. You can verify this by including this within and outside the Observable.

console.log(“Inside/outside observable” + new Date().toLocalTimeString());
Nikhil
  • 1,126
  • 12
  • 26
1

Listing of request/response headers lack information on URL, method and most important response status code.

Seeing Location: http://localhost:8102/login among response headers I can guess that it could be 401 Unauthorized or anything else that redirects to the login page. Hence, if there is an auth filter in the filter chain, it may be a culprit.

The following request headers

Host: localhost:8102
Origin: http://localhost:4200

suggests that you are doing CORS and the CORS filter may be involved indeed and fulfill response before the request gets routed to the controller. I suggest setting a breakpoint into the CORS filter (and into others if any) and debug it to the point where the response is returned.

Oleg Kurbatov
  • 1,376
  • 1
  • 19
  • 32
  • Yes, that's exactly what I did, and I discovered the request was making it to the server, but not my controller method. – CNDyson Jan 08 '19 at 21:52
0

define a proxy.conf.json

{
"/login*": {
    "target":"http://localhost:8080",
    "secure":false,
    "logLevel":"debug"
    }
} 

now in your package.json

"scripts": {
    "start":"ng serve --proxy-config proxy.config.json"
}

I think there is issue while getting connection in both webapp.please try .

this_is_om_vm
  • 608
  • 5
  • 23
0

Thanks to everyone who contributed. I discovered the error was due to the headers of my controller method. After changing them, the method was invoked properly. This is what worked:

@RequestMapping(method = RequestMethod.POST, path = "/generateCert", 
    produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE}, consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<byte[]> generateCert(@RequestBody CertInfo certInfo) {
    byte[] responseBytes = commonDataService.generateCsr(certInfo);    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
            .contentLength(responseBytes.length)
            .body(responseBytes);
}
CNDyson
  • 1,687
  • 7
  • 28
  • 63