6

So I am trying to load the data from a REST source into my Angular 6 app using http: HttpClient from '@angular/common/http'. Calling the app in the browser using ng serve --open though doesn't do the job. I assume CORS to be the problem here. I guess I either have to set the server or the client headers with Access-Control-Allow-Origin or something, but I have already tried multiple ways without any success in making this simple REST call work. So what follows below is what I coded.

Calling the Angular app in the browser responds the following error:

Failed to load http://localhost:8080/mysite-backend/rest/report/single/d83badf3: 
Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present on the requested 
resource. Origin 'http://localhost:4200' is therefore not allowed 
access.

Calling the same URL (http://localhost:8080/mysite-backend/rest/report/single/d83badf3) within the Chrome browser works perfectly though:

{"id":"d83badf3","language":"en","categoryId":"a5","title":"Simcity","created":1527723183880,"modified":1527723183880}

Within Angular 6 I use the following service class that I generated with ng generate service tour. Within that I created the method getTour(id: string) which does non more than call REST at the URL and return the retrieved JSON-string as a Tour object:

import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Tour } from './tour';
import { catchError, tap } from 'rxjs/operators';

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json'
  })
};

@Injectable({
  providedIn: 'root'
})
export class TourService {

  // URL to the web api.
  private tourUrl = 'http://localhost:8080/mysite-backend/rest/report';

  constructor(
    private http: HttpClient
  ) { }

  /**
   * 
   * @param id: string GET tour report by id. Will 404 if id not found.
   */
  getTour(id: string): Observable<Tour> {
    httpOptions.headers.append('Access-Control-Allow-Origin', 'http://localhost:8080');
    httpOptions.headers.append('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
    httpOptions.headers.append('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
    httpOptions.headers.append('Access-Control-Allow-Credentials', 'true');

    const url = `${this.tourUrl}/single/${id}`;
    console.log("XXX URL GET " + url)
    return this.http.get<Tour>(url, httpOptions).pipe(
      tap(_ => this.log(`fetched tour id=${id}`)),
      catchError(this.handleError<Tour>(`getHero id=${id}`))
    );
  }

  private handleError<T> (operation = 'operation', result?: T) {
    return (error, any): Observable<T> => {
      console.error(error);
      this.log(`${operation} failed: ${error.message}`);
      return of(result as T);
    };
  }

  private log(message: string) {
    console.log("Log message: " + message);
  }
}

This is the Tour object:

export class Tour {
    id: string;
    language: string;
    categoryId: string;
    created: Date;
    modified: Date;
    title: string;
    content: string;
}

I also added HttpClientModule from '@angular/common/http' to the imports array and the providers array within app.module.ts.

The RESTful WebService is built in Java. Here I have the getReport(@PathParam("reportId") String reportId) method, that gets an Report object and returns it within a Reponse:

import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 * Describes the RESTful access for reports.
 */
@Path("/report")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ReportResource {
    @Inject
    private Logger logger;

    @GET
    @Path("/single/{reportId}")
    public Response getReport(@PathParam("reportId") String reportId) {
        //return Mock.getReport(reportId);
        return Response.ok() // 200
                       .entity(Mock.getReport(reportId))
                       .header("Access-Control-Allow-Origin", "*")
                       .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
                       .allow("OPTIONS").build();
    }
...
}

What must I do to make a successful call from the Angular 6 client to the Java RESTful WebService?

Socrates
  • 8,724
  • 25
  • 66
  • 113
  • @PaulSamsotha Not a duplicate. Found it out. The problem here is `ng serve` which has to be configured with `--proxy-config proxy.config.json`. The configuration file needs to have a setting for `"changeOrigin": true`. – Socrates May 31 '18 at 14:42
  • Having found that out after many hours, it now works perfectly. Still, I can't wrap my head around the sense of CORS other that it is utterly painful to have as a hidden entrapment costing a lot of time to find out what it is while not providing any more security. So if someone is willing to give an answer to that, I'll gladly accept the answer. – Socrates May 31 '18 at 14:45
  • 1
    https://stackoverflow.com/a/50241292/2587435 – Paul Samsotha May 31 '18 at 14:51
  • @PaulSamsotha The link solved the matter from the backend perspective. Took a couple of hours to understand, but works like a charm now. – Socrates Jun 02 '18 at 00:37
  • @PaulSamsotha Still have a problem. Every time I access the REST api through the Angular app, I loose my session. On every access he session is renewed. Tried `withCredentials: true` on client and server side, didn't work though. This only happens when the URL is different. If the URL is same, the session works perfectly. Is that CORS again? – Socrates Jun 02 '18 at 01:05
  • I don't know. I generally never use sessions when working with REST APIs and SPAs, and that's all I work with. So I don't have a lot of working knowledge as far as working with sessions is concerned. – Paul Samsotha Jun 02 '18 at 02:55
  • I think this address will help you more: https://stackoverflow.com/a/53587709 – ilmaz Jun 10 '19 at 08:16

5 Answers5

4

The Issue is not related to angular itself, but the the web server you are using.

The angular http client request always have a preflight request with type options before hitting the actual request.

you might need to add OPTIONS request method to this line

   .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
Ihab Salem
  • 83
  • 5
  • Where do you think I should add `OPTIONS`? I'm asking because I do already have OPTIONS added within my Angular client call: `httpOptions.headers.append('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');` – Socrates May 31 '18 at 00:13
  • 1
    The allowing should be from the server. try to replace the line in the getReport Java function .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT") with .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") – Ihab Salem May 31 '18 at 01:54
  • https://stackoverflow.com/questions/50614896/angular-6-accessing-rest-failing-with-access-control-allow-origin/60085982#60085982 – Agnel Amodia Feb 06 '20 at 00:18
0

I almost spent day with resolving the problem. First I thought issue is from angular side but it seems that issue was from server side. You need to add @CrossOrigin annotation above the get method.

@GET
@CrossOrigin(origins = "http://localhost:4200")
@Path("/single/{reportId}")
public Response getReport(@PathParam("reportId") String reportId) {
    //return Mock.getReport(reportId);
    return Response.ok() // 200
                   .entity(Mock.getReport(reportId))
                   .header("Access-Control-Allow-Origin", "*")
                   .header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT")
                   .allow("OPTIONS").build();
}
0

I wasted almost a day in figuring out at client side angular application but the problem actually was at server side nodejs application to allow CORS.

app.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); });

Agnel Amodia
  • 765
  • 8
  • 18
-1

The following steps helped me to resolve it:

1- create proxy.conf.json file under your project root instead of src folder

3- Add code as below in proxy.conf.json file.

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false
  }
}

4- add this exactly in package.json under scripts "start": ng serve --proxy-config proxy.conf.json

5- do npm start instead of ng serve

Murtaza Bharmal
  • 472
  • 4
  • 16
Kmaj
  • 61
  • 3
  • 10
-1

I solved CORS problem using proxy.

  1. Create a file proxy.conf.json at the same level as package.json.
  2. add following content

{
    "/api/*": {
        "target": "http://external-domain-you-wanted-to-access",
        "secure": false,
        "changeOrigin": true
    }
}
  1. Run the command ng serve --proxy-config proxy.conf.json

Reference link

Pruthviraj
  • 560
  • 6
  • 23