41

I'm improving an existing API, and the requirement is to provide a single get method which can accept multiple search criteria and based on those criteria perform the query.

I'm using Spring MVC. The get method signature:

@GetMapping("/subscribers")
public ResponseEntity<List<SubscriberDTO>> getAllSubscribers(Pageable pageable, @RequestBody List<SearchCriteria> lstSearchCriteria)

The implementation is working as expected tested on Postman

Now I was going to Angular to implementing the front end and I can't find a way to send a body through HttpClient Get method...

I'm kind of stuck. Should I send the search criteria over headers? Or there's a better way of doing it?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Bruno Miguel
  • 1,003
  • 3
  • 13
  • 26
  • 4
    GET requests shouldn't have bodies (see e.g. https://stackoverflow.com/questions/978061/http-get-with-request-body). I think you *can* do it through the HttpClient's general request method, but I wouldn't count this as an improvement to the API. Don't use headers, either; use *query parameters*. – jonrsharpe Jan 12 '19 at 21:53
  • Thank you for your reply – Bruno Miguel Jan 12 '19 at 21:58

5 Answers5

34

As far as I can tell you cannot use HttpClient get to send a body. You could use the query at least for it to be idiomatic. (Or you can try to force the issue somehow).

Lets say you have an array with your criteria:

const criteria = [ {a: 25}, {b: 23} ];
http.get(url + '/?criteria='+ encodeURIComponent( JSON.stringify(criteria)));

Sending a body in a GET request is a violation of some RFC standard, and even though it might work you're bound to summon some arcane demons.

Nuno Sousa
  • 832
  • 7
  • 15
  • 16
    would you mind adding a reference to your statement 'violation of some RFC standard' to support it? – jarodsmk Dec 23 '19 at 12:55
  • 3
    RFC2616 section 4.3 and sections 5.1.1 - though in truth it's pretty vague and recommends that web servers discard the body when it's not allowed based on the request METHOD - I know most web servers do discard the BODY lines on a GET request. Indeed there is even some mods for apache to invert this behaviour. – Nuno Sousa Jan 13 '20 at 16:57
  • 1
    has been removed – JFFIGK Nov 28 '20 at 17:17
  • 1
    RFC7231 section 4.3.1 implies that a body in a GET request is allowed but not well defined. – Bruce Nov 01 '21 at 05:15
  • Of course you can. HttpClient.request you can specify anything you want including body and method ("get" in our case) – user2555515 May 17 '23 at 15:07
8

In the service method, you can have your method like below which takes the optional parameters for the search query. And you can send your search parameter over Observe as below.

getAllSubscribers(
    page?,
    itemsPerPage?,
    userParams?
  ): Observable<PaginatedResult<Subscribers[]>> {
    const paginatedResult: PaginatedResultSubscribers[]> = new PaginatedResult<
      Subscribers[]
    >();

    let params = new HttpParams();

    if (page != null && itemsPerPage != null) {
      params = params.append("pageNumber", page);
      params = params.append("pageSize", itemsPerPage);
    }

    if (userParams != null) {
      params = params.append("minAge", userParams.minAge);
      params = params.append("maxAge", userParams.maxAge);
        }

       return this.http
      .get<Subscribers[]>(this.baseUrl + "/subscribers", { observe: "response", params })
      .pipe(
        map(response => {
          paginatedResult.result = response.body;
          if (response.headers.get("Pagination") != null) {
            paginatedResult.pagination = JSON.parse(
              response.headers.get("Pagination")
            );
          }
          return paginatedResult;
        })
      );
  }

The basic idea is the use of HttpParams.

Amir
  • 1,855
  • 3
  • 24
  • 40
  • 2
    Keep in mind that `HttpParams` is **immutable**. I spent a while figuring out why `params.append()` didn't work. – Odys Dec 29 '20 at 19:22
  • 1
    @Odys -Good Point. You can do new HttpParams().set(minAge", userParams.minAge).set("maxAge", userParams.maxAge) – Morez Mar 16 '21 at 09:55
4

Just to clarify some of the answers here, firstly, as stated Angular does not support supplying a body with a GET request, and there is no way around this. The reason for that is not Angular's fault but that of XMLHttpRequest (XHR), the API that browsers use for making requests. XHR does not support body with GET requests.

There is nothing in the current HTTP standards that says a Body with a GET request is invalid, only that it is not semantically well defined.

In terms of REST, in my opinion, supplying a body with a GET request is much preferable to using POST. So it is somewhat annoying that XHR does not support this.

cbp
  • 25,252
  • 29
  • 125
  • 205
  • 1
    Sure Angular supports it, See about using "request" method. Also in term of REST using body with GET can cause hard to track issues in the future. You will wonder why the application stopped working when "nothing" changed. It can take some efforts to realize that some network component like proxy or new Kubernetes setup does not support gets with body. Don't ask me how I know. – user2555515 Nov 28 '22 at 20:12
2

There is an ongoing debate whether get request can have body or not. That I think is not a subject of this thread. If you do need to make get request with body ( for example the server imposes that) you can use request method:

request(method: string, url: string, options: {
        body?: any;
        headers?: HttpHeaders | {
            [header: string]: string | string[];
        };
       //.......
        
    }): Observable<ArrayBuffer>;
user2555515
  • 779
  • 6
  • 20
  • I don't think this is valid. I'm currently trying this out and while this does compile the end GET request is still missing a body. – MBender May 30 '23 at 14:49
  • @MBender You do realize that you are making claim that the angular standard implementation is faulty. Are you checking it on the server side ? Try making a request using curl and I bet you get the same result. The problem is probably in your overall networking setup, not the client. – user2555515 Jun 05 '23 at 21:42
  • @MBender That is exactly opposite to your previous statement. In any case inspect your code and setup. – user2555515 Jun 06 '23 at 15:50
  • Sorry, I messed up my last comment and didn't remember correctly. If I DO remember correctly the network inspection in chrome clearly didn't have a body for the GET request when using this. Unlikely to be a network issue. – MBender Jun 07 '23 at 12:11
0

To make Amir's answer some more generic I have done the following to build params for the passed item.

To my generic data service I have added this variant:

// Get data for passed parameters or item containing filters
public getByItem<T>(apiMethod: string, item?: T, apiParms?: HttpParams): Observable<T> {
    if (!apiParms) apiParms = new HttpParams();
    if (item) {
        const keys = Object.keys(item) as Array<keyof T>;
        for (let key of keys) {
            apiParms = apiParms.append(key.toString(), item[key].toString());
        }
    }
    // Call generic method of data service
    return this.get<T>(apiMethod, apiParms); // => return this.http.get<T>(environment.apiUrl + apiMethod, { headers: this.defaultHeaders