Как заставить Angular2 POST с помощью x-www-form-urlencoded

У меня есть проект, который должен использовать Angular2 (окончательный вариант) для публикации на старом унаследованном сервере Tomcat 7, предоставляющем API-интерфейс REST-ish с использованием страниц.jsp.

Это работало нормально, когда проект представлял собой простое приложение JQuery, выполняющее запросы AJAX. Тем не менее, масштабы проекта выросли так, что его необходимо будет переписать с использованием более современных рамок. Angular2 выглядит фантастически для этой работы, за одним исключением: он отказывается выполнять запросы POST, используя что-либо, кроме как данные формы, которые API не извлекает. API ожидает, что все будет urlencoded, полагаясь на Java request.getParameter("param") синтаксис для извлечения отдельных полей.

Это отрывок из моего user.service.ts:

import { Injectable }    from '@angular/core';
import { Headers, Response, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class UserService {
    private loggedIn = false;
    private loginUrl = 'http://localhost:8080/mpadmin/api/login.jsp';
    private headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});

    constructor(private http: Http) {}

    login(username, password) {
        return this.http.post(this.loginUrl, {'username': username, 'password':  password}, this.headers)
            .map((response: Response) => {
                let user = response.json();
                if (user) {
                    localStorage.setItem('currentUser', JSON.stringify(user));
                }
            }
        );
    }
}

Независимо от того, какой я установил тип содержимого заголовка, он всегда заканчивается как не закодированные данные формы. Это не в честь заголовка, который я устанавливаю.

кто-нибудь еще сталкивался с этим? Как вы заставляете Angular2 передавать данные POST в формате, который может быть прочитан старым Java API с помощью request.getParameter("param")?

Изменить: Для тех, кто найдет это в будущем, решение на самом деле очень просто. Установите тело сообщения следующим образом:

let body = `username=${username}&password=${password}`;`

Смотрите пример Брэда ниже.

14 ответов

Решение

Вы можете сделать это используя URLSearchParams поскольку тело запроса и angular автоматически установят тип контента в application/x-www-form-urlencoded и кодировать тело правильно.

let body = new URLSearchParams();
body.set('username', username);
body.set('password', password);

this.http.post(this.loginUrl, body).map(...);

Причина, по которой он в данный момент не работает для вас, заключается в том, что вы не кодируете данные тела в правильном формате и неправильно настраиваете параметры заголовка.

Вам нужно закодировать тело следующим образом:

let body = `username=${username}&password=${password}`;

Вам нужно установить параметры заголовка следующим образом:

this.http.post(this.loginUrl, body, { headers: headers }).map(...);

Для Angular 4.3+/5+ (New HTTPClient) используйте следующее:

let body = new URLSearchParams();
body.set('user', username);
body.set('password', password);

let options = {
    headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};

this.http
    .post('//yourUrl.com/login', body.toString(), options)
    .subscribe(response => {
        //...
    });

Обратите внимание на 3 вещи, чтобы заставить его работать как положено:

  1. Используйте URLSearchParams для вашего тела
  2. Преобразовать тело в строку
  3. Установите тип содержимого заголовка

Внимание: старым браузерам нужен полифилл! Я использовал: npm i url-search-params-polyfill --save а затем добавлен в polyfills.ts: import 'url-search-params-polyfill';

Для тех, кто все еще ищет ответ, вот как я решил его с помощью Angular 5 и HttpClient:

const formData = new FormData();

// append your data
formData.append('myKey1', 'some value 1');
formData.append('myKey1', 'some value 2');
formData.append('myKey3', true);

this.httpClient.post('apiPath', formData);

НЕ устанавливайте заголовок Content-Type, угловой исправит это для вас!

Вот что сработало для меня с Angular 7:

const payload = new HttpParams()
  .set('username', username)
  .set('password', password);

this.http.post(url, content);

Нет необходимости явно устанавливать заголовок при таком подходе.

Обратите внимание, что объект HttpParams является неизменным. Таким образом, выполнение чего-то вроде следующего не будет работать, оно даст вам пустое тело:

const payload = new HttpParams();
payload.set('username', username);
payload.set('password', password);

this.http.post(url, content);

Угловой 9

Это код, который работает.

Выберите другие варианты, которые подходят вам, чтобы вернуть ответ "неуспешно".

          let params = new HttpParams({
      fromObject: { email: usuario.email, password: usuario.password, role: usuario.role },
    });

    let httpOptions = {
      headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
    };
    
    return this.http.post(`${this.url}/usuario/signup`, params.toString(), httpOptions).pipe(
      map(
        (resp) => {
        
          console.log('return http', resp);
          return resp;
        },
        (error) => {
          console.log('return http error', error);
          return error;
        }
      )
    );

помните из строки, которую вы используете fromString и нет fromObject.

Я нашел это решение после нескольких часов работы над этим вопросом.

login(userName: string, password: string) {
const headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append( 'No-Auth', 'True');

const body = new URLSearchParams();
body.set('username', userName);
body.set('password', password);
body.set('grant_type', 'password');

return this.http.post(
    this.baseUrl + '/token'
   , body.toString()
   , { headers: headers }
  )
  .pipe(map(res => res.json()))
  .pipe(map(res => {
    localStorage.setItem('auth_token', res.auth_token);
    return true;
  }))
  .pipe(catchError((error: any) => {
    return Observable.throw(error);
  }));

}

При использовании угловых форм большинство параметров будут отправлены как объекты, поэтому ваша функция входа, скорее всего, будет иметь этот объектform.value = {username: 'someone', password:'1234', grant_type: 'password'} как параметр

чтобы отправить этот объект как x-www-form-urlencoded, ваш код будет

export class AuthService {
    private headers = new HttpHeaders(
        {
            'Content-Type':  'application/x-www-form-urlencoded',
            Accept: '*/*',
        }
    );
  constructor(private http: HttpClient) { }

  login(data): Observable<any> {
    const body = new HttpParams({fromObject: data});
    const options = { headers: this.headers};
    return this.http.post(`${environment.baseUrl}/token`, body.toString(), options);
  }

Для Angular 12 это сработало для меня.

      
  options = {
    headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
  };

  params = new HttpParams()
  .set("client_id", "client_id")
  .set("client_secret", "client_secret")
  .set("grant_type", "grant_type")
  .set("scope", "scope")

  getToken(){
    return this._http.post(`${URL}`, this.params, this.options)
  }

Кроме того, не забудьте импортировать следующее вверху import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';

Также обратите внимание, что, в отличие от других, мы не используем функцию toString(), так как она избыточна.

Добавляя из Brad, тело вам нужно будет вызвать body.toString() и поставить его как тело запроса.

Угловой 8

const headers = new HttpHeaders({
  'Content-Type': 'application/x-www-form-urlencoded'
});
const params = new HttpParams();
params.set('username', 'username');
params.set('password', 'password');

this.http.post(
  'https://localhost:5000/api',
  params.toString(),
  { headers }
);
export class MaintenanceService {

  constructor(private http: HttpClient) { }

  //header de requete http
  private headers = new HttpHeaders(
    {  'Content-Type':  'application/x-www-form-urlencoded' }
  );





// requete http pour recuperer le type des maintenances
 createMaintenance(data: createMaintenance){
  const options = { headers: this.headers};
   return this.http.post('api/v2/admin/maintenances', data, options ).subscribe(status=> console.log(JSON.stringify(status)));
 }

Ребята, я работал над этим некоторое время, и благодаря этому сообщению Джоша Морони https://www.joshmorony.com/integrating-an-ionic-application-with-a-nodejs-backend/ я понял, что проблема была. По сути, когда я начал тестировать свой api, я использовал POSTMAN, и он работал отлично, но когда дело дошло до его реализации с помощью Ionic Angular, это стало проблемой. Решение в этом посте касается только импортаbody-parser и использовать его как промежуточное ПО для приложений, как это app.use(bodyParser.json()) в корневом файле (индексе) на стороне сервера.

Надеюсь, это поможет, спасибо!

      let options = {
    headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
let body = new URLSearchParams();

body.set('userId', userId);
body.set('discussionId', discussionId);

Мне удалось решить проблему, используя следующий блок кода:

      const body = new URLSearchParams();
body.set('username', username);
body.set('password', password);

const headers = new HttpHeaders()
      .append(
         'Content-type',
         'application/x-www-form-urlencoded; charset=UTF-8');

this.http.post(this.loginUrl, body, { headers }).map(...);
Другие вопросы по тегам