แทรกการทำงานของ HttpClient ขณะที่ Request และ Response ตอนที่ 4

เขียนเมื่อ 6 ปีก่อน โดย Ninenik Narkdee
httpclientmodule httpclient interceptor angular

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ httpclientmodule httpclient interceptor angular

ดูแล้ว 12,275 ครั้ง


ก่อนเข้าสู่รายละเอียดเนื้อหาในตอนที่ 4 นี้ ขอย้อนไปเพิ่มเติมในส่วนของการกำหนดค่า สำหรับการ
ใช้งานการส่งข้อมูลไปยัง backend service ต่อจากตอนที่แล้วเล็กน้อยดังนี้ ทบทวนเนื้อหาตอนที่แล้ว
ได้ที่บทความ
    การส่งข้อมูลไปยัง backend service ด้วย HttpClient ใน Angular ตอนที่ 3 http://niik.in/854 
 
ไฟล์ register.component.ts บางส่วนจากตอนที่แล้ว ในการส่งข้อมูล
 
  onSubmit(f:any){
    let data = f.value;
    this.http.post(this.urlApi+'show_data.php',data)    
    .subscribe(result =>{
      this.responseValue = result;
      console.log(result);
    },
    ( err:HttpErrorResponse ) => {
      // กรณี error
      if (err.error instanceof Error) {
        // กรณี error ฝั่งผู้ใช้งาน หรือ การเชื่อมต่อเกิด error ขึ้น
        console.log('An error occurred:', err.error.message);
      }else{ // กรณี error ฝั่ง server ไม่พบไฟล์ ,server error 
        console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
      }       
    });
  }
 
ในการใช้งาน post() method เราสามารถกำหนดการตั้งค่าเพิ่มเติม สำหรับค่าที่ต้องการส่งไปกับข้อมูล
และการกำหนดค่าอื่นๆ ได้ตามรูปแบบต่อไปนี้
 
post(url: string, body: any | null, options: {
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: HttpObserve;
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType?: 'arraybuffer' | 'blob' | 'json' | 'text';
    withCredentials?: boolean;
} = {}): Observable<any>
 
มาดูตัวอย่างการส่งค่า URL Parameters และ Header ไปยังไฟล์ api สามารถกำหนดได้ดังนี้
 
this.http.post(this.urlApi+'show_data.php',data,{
  headers: new HttpHeaders().set('X-Custom-Header', 'my-header'),
  params: new HttpParams().set('id', '3')
})   
 
เมื่อมีการใช้งาน HttpParams และ/หรือ HttpHeaders เราต้อง import สองค่านี้มาใช้งานด้วย
 
import { HttpClient, HttpHeaders, 
        HttpErrorResponse, 
        HttpResponse,
        HttpParams } from '@angular/common/http';
 
จากตัวอย่างเราสามารถกำหนด name และ value ของ header ที่ต้องการส่งไปตามรูปแบบข้างต้น
สำหรับค่า params หรือ URL Parameter นั้น จะเป็นการกำหนด URL query string ต่อท้ายไปยัง url 
ของ api ที่เราเรียกใช้ อย่างในตัวอย่าง เราเรียกไปยังไฟล์ที่ url http://localhost/demo/show_data.php
การกำหนด params: new HttpParams().set('id', '3') ก็จะทำให้ รูปแบบ url มีค่า url parameter
ดังนี้
 
http://localhost/demo/show_data.php?id=3  
 
ดังนั้นเราสามารถอ้างอิงตัวแปร $_GET['id'] ที่ส่งไปยัง api ได้ ตัวอย่างไฟล์ show_data.php ส่วนของการรับค่า
 
<?php
header('Access-Control-Allow-Origin: *'); 
header('Access-Control-Allow-Headers: Content-Type,X-Custom-Header'); 
header('Content-type: application/json');
//require_once("dbconnect.php");
$jsonData = array();	
if($_SERVER['REQUEST_METHOD'] == "GET") {
	echo json_encode($jsonData);
}elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS"){
	echo json_encode($jsonData);
}elseif($_SERVER['REQUEST_METHOD'] == "POST"){
	$json = file_get_contents('php://input');
	$postData = json_decode($json, TRUE);
/*	$mysqli->query(
	"
	INSERT INTO tbl_data SET 
	data_text='".$postData['fullname']."'
	"
	);	*/
	// $_GET['id'] เรียกค่าตัวแปร GET ได้
	echo json_encode(array(
		"statusCode"=>"200 OK",
		"statusMessage"=>"Successful",
		"getData"=>$_GET,
		"postData"=>$json
	));
}else{
    header("HTTP/1.1 403 Access Forbidden");
	echo json_encode(array(
		"statusCode"=>"403 Access Forbidden",
		"statusMessage"=>"Fail"
	));
}
?>
 
สามารถดูเพิ่มเติมเกี่ยวกับการกำหนด option ให้กับ post() method ได้ที่
 
 
กลับมาที่หัวข้อบทความของเรา การแทรกการทำงานของ HttpClient ในระหว่างที่มีการส่ง Request 
และรับค่า Response กลับมา เป็นรูปแบบการใช้งานในขั้นสูง เราเรียกวิธีนี้ว่าการ Interceptor
 

การใช้งาน HttpClient ขั้นสูง

    ต่อไปนี้เราจะมาดูการใช้งาน HttpClient ชั้นสูง ซึ่งก่อนหน้านี้เราได้รูจักวิธีการใช้งานแบบพื้นฐาน อย่างการส่งค่า
และการรับค่าที่ส่งกลับมามาใช้งานแล้ว เราจะมาดูการแทรกการทำงานของ HttpClient ที่เรียกว่า Interceptor
 

สร้าง Interceptor

   Interceptor ก็เหมือนกับ service หนึ่ง ที่สร้างขึ้นมา โดยใช้ในการเข้าไปแทรกการทำงานในระหว่างก่อนที่ข้อมูล
จะส่งไปยัง backend service และ/ หรือระหว่างที่ข้อมูลจาก backend service จะส่งกลับมายัง application front-end
ในตัวอย่างนี้เราจะสร้าง ไฟล์ interceptor นี้ไว้ใน RegisterModule  ดังนี้
 
C:\projects\httpclient>ng g class register/my-http-interceptor
 
เราจะได้ไฟล์ my-http-interceptor.ts ในโฟลเดอร์ register module ให้กำหนดรูปแบบการใช้งาน
โดย implements กับ HttpInterceptor ดังนี้
 
ไฟล์ my-http-interceptor.ts
 
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, 
        HttpHandler,
        HttpRequest} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';        

@Injectable()
export class MyHttpInterceptor implements HttpInterceptor {
    
    constructor(){}
    
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      console.log('Interceptor ran..');
      return next.handle(req);
    }

}
 
เมื่อไฟล์นี้ถูกเรียกใช้งาน ก็จะเข้าทำงานในส่วนของคำสั่ง intercept() โดยมีการส่งค่า HttpRequest และ HttpHandler
เข้าไปในฟังก์ชั่น คำสั่ง intercept() นี้จะทำการเปลี่ยนแปลงค่าเกี่ยวกับ request ที่เกิดขึ้น เมื่อทำงานในฟังก์ชั่นเรียบร้อยแล้วจะคืนค่าเป็น Observable
ของข้อมูล HttpEvent กลับออกมา ค่านี้ก็จะถูกนำใช้งานต่อไป
    ในตัวอย่างเรายังไม่ได้ให้ทำการเปลี่ยนแปลงค่าใดๆ แค่ทำการส่งต่อค่า HttpRequest ไปทำงานต่อ 
ผ่านคำสั่ง  next.handle()
 
ในการใช้งาน interceptor class ให้เรา import และเรียกใช้งานในไฟล์ RegisterModule ดังนี้
 
ไฟล์ register.module.ts 
 
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule }   from '@angular/forms';
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { RegisterRoutingModule } from './register-routing.module';
import { RegisterComponent } from './register.component';

import { MyHttpInterceptor } from './my-http-interceptor';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    RegisterRoutingModule
  ],
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: MyHttpInterceptor,
    multi: true,
  }],  
  declarations: [RegisterComponent]
})
export class RegisterModule { }
 
 
ค่า HTTP_INTERCEPTORS เป็นค่าคงที่ multi-provider token ซึ่งมีรูปแบบเป็น array ของ HttpInterceptor 
โดยเรา import เข้ามาใช้งานในบรรทัดที่ 4  และเนื่องจาก HTTP_INTERCEPTORS ไม่ได้เป็นค่า string จึงมีการกำหนด
multi option เท่ากับ true เข้าไป
 
เมื่อเราทำการ กำหนด providers เรียบร้อยแล้ว ตัว interceptor ก็พร้อมจะทำงานทันที ที่มีการใช้งาน HttpClient
อย่างในหน้า register ถ้าเรามีการส่งค่าจากฟอร์ม เมื่อเรากรอกข้อมูล แล้วก่อนส่งข้อมูลไปยัง backend service ตัว 
interceptor ก็จะแทรกการทำงานเข้ามาทันที ดูตัวอย่าง console ตามรูป ด้านล่าง 
 
 

 
 
สังเกตในส่วนของ console เราจะเห็นว่า มีการเข้าไปทำงานในคำสั่ง intercept() แสดงข้อความ "Interceptor ran.."
ก่อนที่จะมีการส่ง request ไปทำงานต่อไปยัง backend service
 

Event ของ Http

    ถ้าเราสังเกตในส่วนของคำสั่ง interceptor() ใน MyHttpInterceptor class ไม่ได้มีการคืนค่าเป็น
Observable<HttpResponse<any>> และกลับเป็นค่า Observable<HttpEvent<any>> ทั้งนี้ก็เพราะ interceptor
ทำงานอยู่ในระดับที่ต่ำกว่าการทำงานของ HttpClient  การ request แล้วเกิด HttpRequest ในแต่ละครั้ง
จะเกิด event ขึ้นได้หลาย event รวมถึง progress event กรณีที่เป็นการดาวน์โหลดหรืออัพโหลด   แต่สำหรับ 
HttpResponse class นั้นจริงแล้วโดยตัวของมันก็เป็น event ที่มี type เป็น HttpEventType.HttpResponseEvent

ลำดับการเรียกใช้ Interceptor

    ในกรณีที่เรามีการเรียกใช้ interceptor หลายค่า Angular จะเรียกใช้หรือเรียกทำงาน interceptor นั้นๆ ตามลำดับ
การกำหนดค่าใน provider array ค่าไหนกำหนดก่อนก็ทำงานตัวนั้นก่อน เป็นต้น

 

การเปลี่ยนแปลงค่าไม่ได้

    ตามที่ได้อธิบายในตอนแรกว่า คำสั่ง interceptor() จะทำการเปลี่ยนแปลงค่า HttpRequest แล้วส่งค่าต่อไปทำงานนั้น
จะไม่ได้หมายถึง การเปลียนแปลงค่าของ HttpRequest โดยตรง แต่จะเป็นการ นำค่า HttpRequest มาสร้างเป็นค่าใหม่
โดยการ copy โดยทำผ่านคำสั้ง clone() จากนั้นเราจึงใช้งานที่ copy มานั้นมาปรับแต่งค่า แล้วส่งค่าต่อไปใช้งาน
    เรามาลองปรับไฟล์ interceptor เพื่อทำการเปลี่ยนแปลงค่า ที่ clone() มาอีกทีดังนี้
 
ไฟล์ my-http-interceptor.ts
 
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, 
        HttpHandler,
        HttpRequest} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';        

@Injectable()
export class MyHttpInterceptor implements HttpInterceptor {
    
    constructor(){}
    
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      console.log('Interceptor ran..');
      const secureReq = req.clone({url: req.url.replace('http://', 'https://')});
      console.log(secureReq.url);
      return next.handle(secureReq);
    }

}
 
ในตัวอย่างเราจะทำการเปลี่ยน url ที่ request ไปยัง backend service จาก http เป็น https ก่อนส่งค่าไป
จะเห็นเราทำการ clone() และแทนค่า url แล้วเก็บค่า HttpRequest ใหม่ไว้ในตัวแปร secureReq แล้วส่ง
ค่านี้ไปทำงาน ในตัวอย่างเราทำการแสดงค่าของ secureReq.url ที่เราส่งไปนั้น ค่ามีการเปลี่ยนแปลงตามที่
เราต้องการหรือใหม่ สังเกตรูปด้านล่าง จะพบว่า มีการเปลี่ยนจาก http เป็น https แต่เนื่องจาก backend service
ที่ทดสอบนี้ไม่รอบรับ จึงอาจจะแสดง error แต่ตรงนี้ไม่ใช่ประเด็น สิ่งที่เราต้องการแสดงคือ เราสามารถเปลี่ยนแปลง
ค่าโดยใช้วิธีการสร้าง ค่าใหม่ด้วยการ clone แต่เราจะไม่สามารถเปลี่ยนแปลงค่า HttpRequest เดิมได้
 
 

 

 

การกำหนดค่า header ใหม่

    จากรูปแบบวิธีการเปลี่ยนแปลงค่า HttpRequest โดยใช้การเปลี่ยนแปลงจากค่า ที่ clone ข้างต้นที่ได้กล่าวไปแล้ว
เราสามารถใช้รูปแบบข้างต้นในการเปลี่ยนแปลงค่า header ดังนี้
 
ไฟล์ my-http-interceptor.ts บางส่วน
 
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
	console.log('Interceptor ran..');
	const authReq = req.clone({headers: req.headers.set('X-Custom-Header', 'x-header-value')});
	return next.handle(authReq);  
}
 
หรือกำหนดในรูปแบบย่อดังนี้
 
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
	console.log('Interceptor ran..');
	const authReq = req.clone({setHeaders: {'X-Custom-Header': 'x-header-value'}});
	return next.handle(authReq);  
}
 
 
นอกจากนั้นเราสามารถใช้งาน interceptor ในการจัดการส่วนต่างๆ เหล่านี้ได้ เช่น
  •     ใช้ในการการยืนยันตัวต้น / การขอสิทธิ์การใช้งาน
  •     ใช้ในการแคช 
  •     ใช้ในการป้องกัน XSRF
 
 
ตอนนี้เราได้ทำความรู้จักการทำการแทรกการทำงานในส่วนของ HttpRequest หรือก่อนที่ข้อมูลจะส่งไปยัง backend service
ไปบ้างแล้วข้างต้น ต่อไปเราจะมาดูในส่วนของการแทรกการทำงานเข้าไปส่วนของ HttpResponse หรือหลังจากที่ข้อมูล
ส่งกลับมาก่อนที่จะส่งไปถึง application front-end โดยเรามีการใช้งาน operator do() ของ Observable ทำงานและ
ตรวจดักค่าที่ส่งกลับมาดังนี้
 
ไฟล์ my-http-interceptor.ts
 
import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, 
        HttpHandler,
        HttpResponse,
        HttpRequest} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';     
import 'rxjs/add/operator/do';   

@Injectable()
export class MyHttpInterceptor implements HttpInterceptor {
    
    constructor(){}
    
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        console.log('Interceptor ran..');
        const started = Date.now();  // เวลาเริ่มต้นที่ request          
        return next
        .handle(req)
        .do(event => {
            if (event instanceof HttpResponse) {
                // หาช่วงเวลาที่ใช้ในการรับส่งข้อมูล เวลาที่ response ส่งค่ากลับมา ลบด้วยเวลา เริ่มต้น
                const elapsed = Date.now() - started; 
                // แสดง url ที่ทำการ request ไป ว่าใช้เวลาไปกี่ มิลลิวินาที
                console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
            }
        });                  

    }

}
 
จากรูปแบบเดิม เราจะ return ค่าของ HttpRequest ที่มีการแปลงค่าแล้วค่อยส่งข้อมูลไปยัง backend service แต่คราวนี้
เราใช้ do operator เพื่อทำการส่งค่าไปก่อน แล้วรอรับค่าที่ส่งกลับมาจาก backend service เมื่อมีค่าส่งกลับมาจะมาในรูป
แบบ event หรือก็คือ HttpResponse นั่นเอง
    ตามที่ได้บอกไปแล้วในตอนต้นว่า HttpResponse นั้นโดยตัวของมันแล้วก็คือ event ของ http สำหรับกรณี response 
ค่ากลับมานั่นเอง ตัว interceptor ข้างต้น เราให้ทำการตรวจจับเวลาเริ่มต้นตั้งแต่มีการส่ง request ไป แล้วส่ง response 
กลับมา เพื่อจะหาว่าการเรียกใช้ http ครั้งนี้ใช้เวลาไปเท่าไหร่ 
    โดยหลังจากสั่งค่ากลับมา เราได้ทำการแทรกการทำงานไปในส่วนของ response โดยตรวจสอบว่า  event เป็นค่าจาก HttpResponse หรือไม่ 
 
ดูตัวอย่างผลลัพธ์การทำงาน
 
 

 
 
เราทราบแต่ต้นว่าในการส่งค่า จะมีการส่งค่าไปสองครั้ง ครั้งแรกเป็น OPTION และ ก็คืนค่ากลับมา แต่ไม่ใช่ค่าที่จะถูกนำ
ไปใช้งาน ครั้งที่สองเป็นการส่งค่า POST ข้อมูล และก็คืนค่ากลับมาเช่นกัน และเป็นค่า HttpResponse ที่จะถูกนำไปใช้งาน
ต่อ จะเห็นว่า ตัวอย่างข้างต้น เรามีการแทรกการทำงานเข้าไปใน event ของ HttpResponse เพื่อคำนวณเวลาที่ใช้นั่นเอง
 
ตอนนี้เราได้รู้จักการใช้งาน interceptor ขั้นสูงไปบางส่วนแล้ว ในตอนหน้า เราจะมาดูการใช้งาน interceptor กันต่อ
ในเรื่องของการ cache ,การใช้งาน progress event กรณีการอัพโหลดไฟล์ และเรื่องการป้องกัน XSRF หรือ CSRF
(Cross-Site Request Forgery (XSRF)) รอติดตาม
 


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ