如何使用x-www-form-urlencoded强制Angular2进行POST


71

我有一个项目需要使用Angular2(最终版本)发布到旧的,旧的Tomcat 7服务器,该服务器使用.jsp页面提供某种类似于REST的API。

当项目只是一个执行AJAX请求的简单JQuery应用程序时,此方法就可以很好地工作。但是,该项目的范围已经扩大,因此需要使用更现代的框架来重写它。Angular2看起来很不错,但有一个例外:它拒绝使用任何选项执行POST请求,但不使用API​​不会提取的表单数据。该API希望所有内容都采用urlencode,依靠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以旧Java API可以使用读取的格式发布数据request.getParameter("param")

编辑:对于以后发现此问题的其他人,解决方案实际上非常简单。像这样设置帖子的正文:

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

请参阅下面的Brad示例。


1
我认为棱角分明的发布难度如此之高www-form-urlencoded。应该有一流的支持才能轻松做到这一点。
山羊

1
这似乎不适用于Angular4 +和HttpClient。
stt106 '17

您是正确的,@ stt106。该解决方案是旧的,并且特定于4之前的版本。如果有时间,我将为HttpClient重写以上解决方案。
戴蒙·卡斯威尔

Answers:


97

2020年6月更新:此答案已有4年历史,并且由于Angular中的API更改而不再有效。请参考最新的答案以获取当前版本方法。


您可以将其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(...);

4
尝试了URLSearchParams()选项,该选项不起作用,但是您对主体的格式绝对正确。做到了。标记为答案,我将更新此帖子以供其他人查找。
戴蒙·卡斯威尔

URLSearchParams用作表单数据的主体,没有任何问题。我认为它是在RC4中引入的。您使用的是哪个版本的angular?
布拉德

最终,但回首过去,我敢肯定我只是打错了打字,而且由于第二种选择有效,所以我没有费心尝试。再次感谢您的协助。
戴蒙·卡斯威尔

9
@DamonKaswell对我来说,使用URLSearchParams作为正文也不会成功。如果我这样做,则内容类型为application / json,并且发布的json数据为空对象。我使用的是Angular 4.0。我最终做了这个`const headers = new Headers({'Content-Type':'application / x-www-form-urlencoded'}); const options = new RequestOptions({headers:headers}); 常量正文:URLSearchParams = new URLSearchParams(); body.set('用户名',用户名); body.set('password',password); 返回this.http.post(API_URL +'login',body.toString(),options)`
joensson

如果您是手动构建主体字符串,请记住对用户名和密码进行编码,否则某些字符将无法正确传递给服务器...
Dawson Toth

97

对于Angular 4.3 + / 5 +(新的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. 设置标题的内容类型

注意:较旧的浏览器确实需要polyfill!我用过:npm i url-search-params-polyfill --save然后添加到polyfills.ts中:import 'url-search-params-polyfill';


1
伙计们要注意,您需要一个polyfill!我将其添加到答案中。
米克

gh,棱角分明。我们需要编写如此冗长的样板代码来完成如此简单的事情,这真是太疯狂了。
山羊

1
老实说,我认为这很简单。如果要添加更多标题选项怎么办?URLSearchParams也会自动完成x-www-form-urlencoded的生成。我在这里完全看不到任何样板代码。如果这样做,则可以使用新的漂亮的Angular HttpClient创建拦截器并在其中添加标头选项。然后没有重复的代码。Ugh,Angular(实际上很棒!)
Mick

6
一个人不应该使用“新” Angular 5HttpParams代替“旧”URLSearchParams吗?
superjos

2
HttpParams有效并且避免了polyfill,但是它是不可变的,因此您必须使用body = body.set('user',username);。等
Wolfgang Stengel,

36

对于那些仍在寻找答案的人,这就是我使用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 5 HttpClient解决方案。我一直在将项目迁移到Angular 5,并在考虑简化发布过程。
戴蒙·卡斯威尔

我需要Authorization在标头中包含它,formData还是将其设置在还是将其设置在中RequestOptions
stt106 '17

如何在对象中附加对象?例如:option {详细信息:{名称:xx,年龄:yy}}
saghar.fadaei,

2
这对我不起作用。它最终以Content-Type:multipart / form-data的形式发送数据
Kai G

2
此请求将生成标头“ multipart / form-data”,如果服务器仅接受“ x-www-form-urlencoded”,则该请求将不起作用
A Kunin

25

这就是我在Angular 7中工作的原因:

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

this.http.post(url, payload);

无需使用此方法显式设置标头。

请注意,HttpParams对象是不可变的。因此,执行以下操作将无法正常工作,这将给您带来空白:

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

this.http.post(url, payload);

这条线不this.http.post(url, content);应该是this.http.post(url, payload)吗?
尼尔森·金

@NelsonKing你是对的!感谢您指出了这一点。我更新了答案。
罗伯特·海格纳

1
我在这里遗漏了什么,但是没有对参数进行编码吗?
deanwilliammills

3

我在这个问题上工作了几个小时后才找到解决方案

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);
  }));

}


帮助过我。非常感谢!
穆罕默德·瓦卡斯

URLSearchParams类没有一个toString方法
withoutOne

3

当使用角度形式时,大多数参数将作为对象发送,因此您的登录功能很可能具有该对象 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);
  }

1

角度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 }
);

1

角度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


完美运行
Duc Trung Mai

0

伙计们,我一直从事此工作,感谢Josh Morony的这篇帖子https://www.joshmorony.com/integrating-an-ionic-application-with-a-nodejs-backend/,我弄清楚了问题是。基本上,当我开始测试我的api时,我使用的是POSTMAN,并且运行良好,但是当使用Ionic Angular实现它时,就成了一个问题。这篇文章中的解决方案仅涉及在服务器端根文件(索引)上将body-parser其导入并用作应用中间件app.use(bodyParser.json())

希望这会有所帮助,谢谢!


0
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)));
 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.