【问题标题】:How to force Angular2 to POST using x-www-form-urlencoded如何使用 x-www-form-urlencoded 强制 Angular2 发布
【发布时间】:2017-02-13 06:28:45
【问题描述】:

我有一个项目需要使用 Angular2(最终版)发布到旧的、遗留的 Tomcat 7 服务器,该服务器使用 .jsp 页面提供有点 REST 风格的 API。

当项目只是一个执行 AJAX 请求的简单 JQuery 应用程序时,这很好用。但是,该项目的范围已经扩大,因此需要使用更现代的框架对其进行重写。 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 以旧的 Java API 可以使用request.getParameter("param") 读取的格式发布数据?

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

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

请参阅下面布拉德的示例。

【问题讨论】:

  • 我认为 Angular 很难以 www-form-urlencoded 的形式发帖,这实在是太蹩脚了。应该有一流的支持来轻松​​做到这一点。
  • 这似乎不再适用于 Angular4+ 和 HttpClient。
  • 你是对的,@stt106。这个解决方案比较老了,具体是4之前的版本。有时间我会为HttpClient重写上面的解决方案。

标签: javascript angular


【解决方案1】:

对于 Angular > 4.3(新 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';

【讨论】:

  • 大家注意你需要一个 polyfill!我将其添加到答案中。
  • 呃,棱角分明。我们需要编写如此冗长的样板代码来做如此简单的事情真是太疯狂了。
  • 老实说,我认为这很简单。如果您想添加更多标题选项怎么办?此外,x-www-form-urlencoded 的生成是由 URLSearchParams 自动完成的。我在这里根本看不到任何样板代码。如果你这样做了,使用新的漂亮的 Angular HttpClient,你可以创建一个Interceptor 并在那里添加你的标题选项。那么没有重复的代码。呃,Angular(实际上很棒!)
  • 不应该使用“新”Angular 5 HttpParams 而不是“旧”URLSearchParams
  • HttpParams 有效并避免了 polyfill,但它是不可变的,因此您必须使用 body = body.set('user', username);等
【解决方案2】:

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(...);

【讨论】:

  • 尝试了 URLSearchParams() 选项,该选项不起作用,但您对正文的格式绝对正确。那成功了。标记为答案,我将更新帖子以供其他人查找。
  • 我使用URLSearchParams 作为表单数据的主体,没有任何问题。我认为它是在 RC4 中引入的。你用的是什么版本的角度?
  • 最终,但回头看,我很确定我只是打错了,因为第二个选项有效,我没有费心重试。再次感谢您的帮助,顺便说一句。
  • @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 = 新 URLSearchParams(); body.set('用户名', 用户名); body.set('密码', 密码); return this.http.post(API_URL + 'login', body.toString(), options)`
  • 如果您手动构建正文字符串,请记住对您的用户名和密码进行编码,否则某些字符将无法正确传递到服务器...
【解决方案3】:

对于那些仍在寻找答案的人,这是我使用 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);

NOT 设置 Content-Type 标头,Angular 将为您解决这个问题!

【讨论】:

  • 不错!感谢您提供 Angular 5 HttpClient 解决方案。我一直在将我的项目迁移到 Angular 5,并正在考虑简化发布过程。
  • 我需要在标题中包含Authorization,我是在formData 上设置它还是在RequestOptions 中设置它?
  • 如何在对象中追加对象?例如:选项{详细信息:{姓名:xx,年龄:yy}}
  • 这对我不起作用。它最终将数据作为 Content-Type: multipart/form-data 发送
  • 这个请求会生成header“multipart/form-data”,如果服务器只接受“x-www-form-urlencoded”,它将不起作用,
【解决方案4】:

这就是 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);

【讨论】:

  • 我在这里遗漏了什么,但这不会对参数进行编码吗?
  • 谢谢大佬,搜索了2个小时后,这个解决方案终于救了我。谢谢我的兄弟
  • @deanwilliammills 是的,它会跳过编码。
【解决方案5】:

我在这个问题上工作了几个小时后发现了这个解决方案

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 方法
【解决方案6】:

当使用 Angular 表单时,大多数参数将作为对象发送,因此您的登录函数很可能有这个对象 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);
  }

【讨论】:

    【解决方案7】:

    Angular 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的字符串。

    【讨论】:

      【解决方案8】:

      伙计们,我一直在研究这个问题,感谢 Josh Morony https://www.joshmorony.com/integrating-an-ionic-application-with-a-nodejs-backend/ 的这篇文章,我发现了问题所在。基本上,当我开始测试我的 api 时,我使用的是 POSTMAN,它运行良好,但是当使用 Ionic Angular 实现它时,它就成了一个问题。这篇文章中的解决方案只是关于导入body-parser 并将其用作应用程序中间件,例如在服务器端根文件(索引)上的app.use(bodyParser.json())

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

      【讨论】:

        【解决方案9】:

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

        【讨论】:

        • 这不起作用。 HttpParams 是不可变的,因此使用这样的集合将不起作用并给出一个空的主体。
        【解决方案10】:
        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)));
         }
        

        【讨论】:

          【解决方案11】:
          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);
          

          【讨论】:

          • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
          猜你喜欢
          • 2020-08-12
          • 2018-12-25
          • 2021-03-14
          • 1970-01-01
          • 2020-09-12
          相关资源
          最近更新 更多