0

What I try to achive: I've got service method that generate PDF/CSV on my backend and I want to save that pdf by pressing a button on my frontend.

My first attempt was to create file and send whole PDF/CSV by controller.

@PostMapping(value = "/export")
public File exportReport(
        @RequestParam(value = "format", defaultValue = "PDF") ExportFileFormat format,
        @RequestBody ExportBody exportBody) {
    if (format.equals(ExportFormat.CSV)) {
        return reportService.csvExportSummaryCustomerReport(exportBody);
    }
    if (format.equals(ExportFormat.PDF)) {
        return reportService.pdfExportSummaryCustomerReport(exportBody);
    }
    throw new InvalidWorkingTimeSyntaxException(String.format("Format:%s is invalid.", format));
}

But this solution gave me an errors with

Access to XMLHttpRequest at 'file:///C:/Users/UserFolder/AppData/Local/Temp/csv6677594787854925068.csv' from origin 'http://localhost:4200' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

Ofc I tried set new set response header with 'Access-Control-Allow-Origin' : '*', but it didn't helped out. Same with chrome.exe --allow-file-access-from-files --disable-web-security.

Thats why I decided to another approach which is transfer bytes[] and on angular side create PDF/CSV file.

@PostMapping(value = "/export")
public ResponseEntity<byte[]> exportReport(
        @RequestParam(value = "format", defaultValue = "pdf") ExportFileFormat format,
        @RequestBody ExportBody exportBody) {
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("Access-Control-Allow-Origin", "*");

    if (format.equals(ExportFileFormat.CSV)) {
        responseHeaders.setContentType(MediaType.valueOf("text/csv"));
        return new ResponseEntity<>(reportService.csvExportSummaryCustomerReport(exportBody),
                responseHeaders,
                HttpStatus.OK);
    }
    if (format.equals(ExportFileFormat.PDF)) {
        responseHeaders.setContentType(MediaType.APPLICATION_PDF);
        return new ResponseEntity<>(reportService.pdfExportSummaryCustomerReport(exportBody),
                responseHeaders,
                HttpStatus.OK);
    }
    throw new InvalidExportFileFormatException(String.format("Format:%s is invalid.", format));
}

Now I added headers and backend seems ok. After that I created service in frontened side:

exportReport(exportBody: ExportBody, format: String): Observable<Object> {
    const exportUrl = `${this.reportsUrl}/export?format=${format}`;

    if (format == "PDF") {
    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/pdf',
            'Accept': 'application/pdf'
        })
    };
      return this.http.post(exportUrl, exportBody, httpOptions);
    }

    if (format == "CSV") {
      const httpOptions = {
        headers: new HttpHeaders({
          'Content-Type': 'text/csv',
          'Accept': 'text/csv'
        })
      };
      return this.http.post(exportUrl, exportBody, httpOptions);
    }
}

Right now I wanted to used it just to print result.

downloadPdf() {
    this.hoursWorkedForCustomersService.exportReport(this.exportBody, "PDF").subscribe(
        result => {
            console.log(result);
            //saveAs(result, 'new.csv');  <- in the future.
        }
    );
}

Obviously in future I would've like to download file as PDF/CSV with e.g.

saveAs(result, 'new.pdf');

I've got an error 406. Response is:

POST http://localhost:4200/export?format=PDF 406.

TypeError: Cannot read property 'message' of null
    at SafeSubscriber.next.handle.do.err [as _error] (error.interceptor.ts:25)
    at SafeSubscriber.__tryOrSetError (Subscriber.js:240)
    at SafeSubscriber.error (Subscriber.js:195)
    at Subscriber._error (Subscriber.js:125)
    at Subscriber.error (Subscriber.js:99)
    at DoSubscriber._error (tap.js:84)
    at DoSubscriber.error (Subscriber.js:99)
    at XMLHttpRequest.onLoad (http.js:1825)
    at ZoneDelegate.webpackJsonp../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:4006)

Any ideas what I'm doing wrong?

degath
  • 1,530
  • 4
  • 31
  • 60
  • 406 means that your backend is not able to generate a response in the requested content type (Accept-Header in request) . What is wondering me, is the fact that in the same request you declare that the body you're sending is the same as you expect. What is the correct Content-Type of ExportBody? – ibexit Oct 25 '18 at 20:49

1 Answers1

2

Try splitting your backend method into two:

@PostMapping(value = "/export", params={"format=PDF"}, produces=MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<byte[]> generatePdf(){..}

@PostMapping(value = "/export", params={"format=CSV"}, produces="text/csv")
public ResponseEntity<byte[]> generateCsv(){..}

And please fix the Content-Type of the request: if you're sending JSON in UTF-8 this should work:

const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json;charset=UTF-8',
            'Accept': 'application/pdf'
        })
    };

BTW: Don't handle CORS headers in your Controller like this:

responseHeaders.set("Access-Control-Allow-Origin", "*");

Consider using @CrossOrigin(origins = "http://localhost:{your frontend server port}") at class or method level on your controller or globally like this:

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurerAdapter() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**").allowedOrigins("http://localhost:{your frontend server port}");
        }
    };
}

cheers

ibexit
  • 3,465
  • 1
  • 11
  • 25
  • "And please fix the Content-Type of the request: if you're sending JSON in UTF-8` this should work:" It seems like when I set in my backend to `produces=MediaType.APPLICATION_PDF_VALUE` or `text/csv` it changes it to `application/pdf` or `text/csv` thats why I cannot set `'Content-Type': 'application/json;charset=UTF-8'` Or am I wrong? After changes I have an error that: `error: SyntaxError: Unexpected token F in JSON at position 0 at JSON.parse` – degath Oct 26 '18 at 05:10
  • And also WebMvcConfigurerAdapter() is deprecated. – degath Oct 26 '18 at 05:13
  • Is this in your frontend: error: SyntaxError: Unexpected token F in JSON at position 0 at JSON.parse? – ibexit Oct 26 '18 at 05:49
  • In order to make sure your backend is working as expected: try postman or curl to issue the post without the frontend. Focus on tehe backend, and if everything is working as 3xpected, take care of the frontend. – ibexit Oct 26 '18 at 05:55
  • Yeah, this is on frontend. Well, I think backend is fine, because I test my Response and it returns 200 with correct body (without difference actually if I send it as "text/csv" and "application/pdf" or both with "application/json"). – degath Oct 26 '18 at 06:19
  • Regarding the deprecation warning please see https://stackoverflow.com/questions/47552835/the-type-webmvcconfigureradapter-is-deprecated – ibexit Oct 26 '18 at 06:23
  • And please haveva look on this: https://webmasters.stackexchange.com/questions/31212/difference-between-the-accept-and-content-type-http-headers – ibexit Oct 26 '18 at 06:48