简体   繁体   English

Angular 9:无法更改或修改 HTTP 标头

[英]Angular 9: Unable to change or modify HTTP headers

My API server runs on Lumen framework and I have a CORS middleware installed there, accompanied with a middleware which manages JSON POST data.我的 API 服务器在 Lumen 框架上运行,我在那里安装了一个 CORS 中间件,以及一个管理 JSON POST 数据的中间件。 On the server side, everything is perfect.在服务器端,一切都很完美。

Now, for the UI, I use Angular 9 with Material layout.现在,对于 UI,我使用 Angular 9 和 Material 布局。 I have a pretty simple login component: it basically validates the input and then calls a service AuthService which communicates with my API server.我有一个非常简单的登录组件:它基本上验证输入,然后调用与我的 API 服务器通信的服务AuthService

AuthService is pretty straightforward: AuthService 非常简单:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpParams } from '@angular/common/http';

export interface Token {
  token: string;
  token_type: string;
  expires_in: number;
}

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  public isAuthenticated = new BehaviorSubject<boolean>(false);

  constructor(
    private http: HttpClient,
    private router: Router
  ) {}

  async checkAuthenticated() {
    const authToken = localStorage.getItem('access_token');
    return (authToken !== null);
  }

  getToken() {
    return localStorage.getItem('access_token');
  }

  async login(username: string, password: string) {
    const api = 'http://api.elektron.test/v1/login';
    const headers = new HttpHeaders({'Content-Type': 'application/json; charset=UTF-8'});

    const body = JSON.stringify({
      email: username,
      password
    });

    // tslint:disable-next-line:max-line-length
    return this.http.post(api, body)
      .subscribe((res: any) => {
        if(res.token) {
          this.isAuthenticated.next(true);
          localStorage.setItem('access_token', res.token);
        }
      }, error => {
        console.error(error);
      }, () => {
        console.log('etstamente');
      });
  }

  async logout(redirect: string) {
    const removeToken = localStorage.removeItem('access_token');

    if (removeToken == null) {
      this.isAuthenticated.next(false);
      this.router.navigate([redirect]);
    }
  }

  handleError(error: HttpErrorResponse) {
    let msg = '';
    if (error.error instanceof ErrorEvent) {
      // client-side error
      msg = error.error.message;
    } else {
      // server-side error
      msg = `Error Code: ${error.status}\nMessage: ${error.message}`;
    }
    return throwError(msg);
  }
}

There in the this.http.post I can pass additional headers in the third argument, and I've tried that already, but I face the problem that whatever header I pass into it, and when I call that request, the Content-Type is never sent.this.http.post我可以在第三个参数中传递额外的标头,我已经尝试过了,但是我面临的问题是,无论我传递给它什么标头,当我调用该请求时, Content-Type永远不会发送。

Another idea I tried was with an interceptor:我尝试的另一个想法是使用拦截器:

import {HttpInterceptor, HttpRequest, HttpHandler, HttpHeaders} from '@angular/common/http';
import { AuthService } from './auth.service';

@Injectable()

export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const token = this.authService.getToken();

    req = req.clone({
      responseType: 'json'
    });

    if (token) {
      req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
    }

    if (!req.headers.has('Content-Type')) {
      req = req.clone({
        setHeaders: {
          'Content-Type': 'application/json',
        }
      });
    }

    // setting the accept header
    req = req.clone({ headers: req.headers.set('Accept', 'application/json') });

    return next.handle(req);
  }
}

When it reaches any of the req.clone statements there, I end up having behavior same as explained before for the POST request.当它到达那里的任何req.clone语句时,我最终的行为与之前解释的POST请求相同。

So, I am clueless what I'm doing wrong here or what's causing this.所以,我不知道我在这里做错了什么或导致这种情况的原因。 In the Chrome browser, in the Console under Network tab when I try to see request headers, when these cases from above are applied, it states Provisional headers are shown - and I've found some Stack Overflow answers on that issue, but none of them solved my issue.在 Chrome 浏览器中,当我尝试查看请求标头时,在“网络”选项卡下的“控制台”中,当应用上述这些案例时,它会显示临时标头- 我已经找到了有关该问题的一些 Stack Overflow 答案,但没有他们解决了我的问题。

I've spent last 5-6 hours searching the net for a solution, tried some of my ideas, all to no avail.我花了 5-6 个小时在网上搜索解决方案,尝试了我的一些想法,但都无济于事。


EDIT: This problem is not related to Angular, but to the server and backend configuration and handling preflight requests .编辑:这个问题与 Angular 无关,而是与服务器和后端配置以及处理预检请求有关

After a sleepless night, I finally resolved the issue, and it was not a problem with Angular or my code in it, but rather configuration of the web server.经过一个不眠之夜,我终于解决了这个问题,这不是Angular或我的代码的问题,而是Web服务器的配置问题。

If anyone experiences anything similar, this answer may provide a potential solution, and this is a book example of a preflight request :如果有人遇到类似的事情,这个答案可能会提供一个潜在的解决方案,这是一个预检请求的书籍示例:

A CORS preflight request is a CORS request that checks to see if the CORS protocol is understood and a server is aware using specific methods and headers. CORS 预检请求是一个 CORS 请求,它使用特定的方法和标头检查 CORS 协议是否被理解以及服务器是否知道。

It is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method, Access-Control-Request-Headers, and the Origin header.它是一个 OPTIONS 请求,使用三个 HTTP 请求标头:Access-Control-Request-Method、Access-Control-Request-Headers 和 Origin 标头。

Meaning that before each of the requests made by the the Angular app, there was an another one which would check the CORS options against the server, via the OPTIONS request.这意味着在 Angular 应用程序发出的每个请求之前,还有另一个请求会通过OPTIONS请求检查服务器的 CORS 选项。

My server setup and Lumen/Laravel CORS setup was failing all the time.我的服务器设置和 Lumen/Laravel CORS 设置一直失败。 This is how my CorsMiddleware looks like from the beginning:这就是我的CorsMiddleware从一开始的样子:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

/**
 * Class CorsMiddleware
 * @package App\Http\Middleware
 */
class CorsMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $headers = [
            'Access-Control-Allow-Credentials' => 'true',
            'Access-Control-Max-Age' => '86400',
            'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PUT, PATCH, HEAD, DELETE',
            'Access-Control-Allow-Headers' => 'X-Requested-With, Content-Type, Accept, Origin, Authorization',
            'Access-Control-Expose-Headers' => 'X-Requested-With, Content-Type, Accept, Origin, Authorization',
            'Access-Control-Allow-Origin' => '*',
        ];

        if ($request->isMethod('OPTIONS')) {
            return response()->json(['method' => 'OPTIONS'], 200, $headers);
        }

        $response = $next($request);

        foreach($headers as $key => $value) {
            $response->header($key, $value);
        }

        return $response;
    }
}

Then this middlware is properly injected in bootstrap/app.php as:然后这个中间件被正确地注入到bootstrap/app.php中:

$app->routeMiddleware([
    'cors' => App\Http\Middleware\CorsMiddleware::class,
    ...,
]);

When this middlware runs, all the headers are sent in the request response, but, this statement in the CorsMiddleware never reached this condition, even if I tried with Postman:当此中间件运行时,所有标头都在请求响应中发送,但是,即使我尝试使用 Postman, CorsMiddleware此语句CorsMiddleware从未达到此条件:

if ($request->isMethod('OPTIONS')) {
    return response()->json(['method' => 'OPTIONS'], 200, $headers);
}

Reason for that was the the routes/web.php didn't handle any OPTIONS request, so that was being handled by the web server itself, and since I used Apache with .htaccess , that also caused some minor issues, but I didn't want to stick to having Apache handling this.原因是routes/web.php没有处理任何OPTIONS请求,所以它是由 web 服务器本身处理的,因为我使用 Apache 和.htaccess ,这也导致了一些小问题,但我没有不想坚持让 Apache 处理这个问题。 I wanted to keep a convention that most of the things are handled by Lumen/Laravel.我想保持一个约定,即大多数事情都由 Lumen/Laravel 处理。

So, in order to have my CorsMiddleware catch the OPTIONS request, I resolved this entire thing with adding this on top of my main routes/web.php file:因此,为了让我的CorsMiddleware捕捉到OPTIONS请求,我通过在我的主routes/web.php文件顶部添加它来解决整个问题:

Route::options('/{any:.*}', ['middleware' => ['cors']]);

Now the pre-mentioned OPTIONS case in the CorsMiddleware which never happend, how does, and this resolved my entire problem.现在前面提到的CorsMiddleware中的OPTIONS案例从未发生过,这是如何发生的,这解决了我的整个问题。

this could be the problem req = req.clone({ responseType: 'json' });这可能是问题 req = req.clone({ responseType: 'json' });

try clonig the request into a new var like: var newReq = req.clone({ responseType: 'json' });尝试将请求克隆到一个新的 var 中,例如: var newReq = req.clone({ responseType: 'json' });

....rest of code... and then return the newReq. ....其余的代码...然后返回 newReq。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM