Hướng Dẫn Triển Khai và Tối Ưu FastCGI Proxying Trong Nginx Hiệu Quả Nhất

Triển Khai và Tối Ưu FastCGI Proxying Trong Nginx

Nginx đã nổi lên như một trong những giải pháp máy chủ web mạnh mẽ và linh hoạt nhất hiện nay. Tuy nhiên, ở cấp độ cơ bản, Nginx chủ yếu được thiết kế như một máy chủ proxy. Với trọng tâm này, Nginx có thể xử lý hiệu quả các yêu cầu từ người dùng và chuyển tiếp chúng đến các máy chủ backend.

Để triển khai FastCGI Proxying trong Nginx, việc chọn VPS Việt Nam chất lượng là yếu tố then chốt. Với tốc độ cao, ổn định và hỗ trợ kỹ thuật 24/7, VPS Việt Nam giúp tối ưu hiệu suất website, đảm bảo trải nghiệm người dùng mượt mà khi xử lý các yêu cầu động.

Nginx hỗ trợ việc chuyển tiếp yêu cầu thông qua các giao thức khác nhau như HTTP, FastCGI, uWSGI, SCGI và memcached. Trong bài viết này, chúng ta sẽ tìm hiểu về proxy FastCGI, một trong những giao thức proxy phổ biến nhất hiện nay.

Tại sao nên sử dụng FastCGI Proxying?

FastCGI proxying trong Nginx thường được sử dụng để chuyển đổi các yêu cầu từ máy khách sang một máy chủ ứng dụng mà bản thân nó không (hoặc không nên) xử lý các yêu cầu trực tiếp. FastCGI là một giao thức phát triển dựa trên CGI (common gateway interface) nhằm cải thiện hiệu suất bằng cách không chạy mỗi yêu cầu dưới dạng một tiến trình riêng biệt. Nó được sử dụng để giao tiếp hiệu quả với một máy chủ xử lý các yêu cầu cho nội dung động.

Một trong những ứng dụng chính của FastCGI proxying trong Nginx là xử lý PHP. Khác với Apache, có thể xử lý PHP trực tiếp nhờ module mod_php, Nginx phải dựa vào một bộ xử lý PHP riêng biệt để xử lý các yêu cầu PHP. Thông thường, việc xử lý này được thực hiện bởi php-fpm – một bộ xử lý PHP đã được kiểm thử rộng rãi để hoạt động cùng với Nginx.

Nginx với FastCGI có thể được sử dụng với các ứng dụng viết bằng ngôn ngữ khác miễn là có một thành phần có thể truy cập được và được cấu hình để phản hồi các yêu cầu FastCGI.

Cơ bản về FastCGI Proxying

Nói chung, proxying yêu cầu bao gồm việc máy chủ proxy (trong trường hợp này là Nginx) chuyển tiếp các yêu cầu từ máy khách đến máy chủ backend. Chỉ thị mà Nginx sử dụng để định nghĩa máy chủ backend để proxy qua giao thức FastCGI là fastcgi_pass.

Ví dụ, để chuyển tiếp bất kỳ yêu cầu nào khớp với định dạng PHP đến một backend chuyên xử lý PHP qua giao thức FastCGI, một khối location cơ bản có thể trông như sau:

# server context

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
}

. . .

Đoạn cấu hình trên sẽ không hoạt động ngay lập tức vì nó cung cấp quá ít thông tin. Mỗi khi thực hiện một kết nối proxy, yêu cầu gốc phải được chuyển đổi để đảm bảo rằng yêu cầu được chuyển tiếp có ý nghĩa với máy chủ backend. Vì chúng ta đang chuyển đổi giao thức với FastCGI, nên việc này đòi hỏi phải có thêm một số bước xử lý.

Trong khi proxy http-to-http chủ yếu liên quan đến việc bổ sung thêm các header http để đảm bảo máy chủ backend có đủ thông tin để phản hồi cho máy chủ proxy thay mặt cho máy khách, thì FastCGI là một giao thức riêng không thể đọc được các header http. Do đó, các thông tin cần thiết phải được truyền cho máy chủ backend thông qua các cách khác.

Phương pháp chính để truyền thêm thông tin khi sử dụng giao thức FastCGI là thông qua tham số. Máy chủ backend cần được cấu hình để đọc và xử lý các tham số này, từ đó điều chỉnh hành vi dựa trên thông tin nhận được. Nginx có thể thiết lập các tham số FastCGI bằng cách sử dụng chỉ thị fastcgi_param.

Cấu hình tối thiểu để thực hiện proxy FastCGI trong kịch bản PHP có thể như sau:

# server context

location ~ \.php$ {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
}

. . .

Trong cấu hình trên, chúng ta thiết lập hai tham số FastCGI là REQUEST_METHODSCRIPT_FILENAME. Cả hai đều là bắt buộc để máy chủ backend hiểu được bản chất của yêu cầu. Tham số thứ nhất cho biết loại hành động mà máy chủ backend cần thực hiện, trong khi tham số thứ hai chỉ định file nào cần được thực thi.

Trong ví dụ này, chúng ta sử dụng các biến của Nginx để thiết lập giá trị cho các tham số này. Biến $request_method luôn chứa phương thức HTTP được yêu cầu bởi máy khách. Tham số SCRIPT_FILENAME được thiết lập bằng cách kết hợp biến $document_root và $fastcgi_script_name. Biến $document_root chứa đường dẫn đến thư mục gốc như được thiết lập bởi chỉ thị root. Biến $fastcgi_script_name sẽ được thiết lập dựa trên URI của yêu cầu. Nếu URI kết thúc bằng dấu gạch chéo (/), giá trị của chỉ thị fastcgi_index sẽ được thêm vào cuối.

Hãy xem thêm ví dụ dưới đây:

# server context
root /var/www/html;

location /scripts {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_index index.php;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

. . .

Nếu location trên được chọn để xử lý một yêu cầu cho /scripts/test/, thì giá trị của SCRIPT_FILENAME sẽ là sự kết hợp của giá trị từ chỉ thị root, URI của yêu cầu và chỉ thị fastcgi_index. Trong ví dụ này, tham số sẽ được thiết lập thành /var/www/html/scripts/test/index.php.

Ở ví dụ này, chúng ta cũng đã chỉ định backend FastCGI bằng cách sử dụng socket Unix thay vì socket mạng. Nginx có thể sử dụng cả hai giao diện này để kết nối đến máy chủ FastCGI. Nếu bộ xử lý FastCGI cùng chạy trên cùng một máy, socket Unix thường được khuyến nghị vì tính bảo mật.

Tách cấu hình FastCGI ra thành các phần riêng biệt

Một nguyên tắc quan trọng trong việc viết mã bảo trì được là tuân theo nguyên tắc DRY (“Don’t Repeat Yourself”) – không lặp lại những thứ đã có. Điều này giúp giảm thiểu lỗi, tăng khả năng tái sử dụng và cho phép tổ chức cấu hình tốt hơn. Vì một trong những khuyến nghị cơ bản khi quản trị Nginx là luôn thiết lập các chỉ thị ở phạm vi rộng nhất có thể, nên mục tiêu cơ bản này cũng áp dụng cho cấu hình Nginx.

Khi xử lý cấu hình proxy FastCGI, hầu hết các trường hợp sử dụng sẽ chia sẻ phần lớn các cấu hình chung. Do đó, và nhờ cách mô hình thừa kế của Nginx hoạt động, gần như luôn có lợi khi khai báo các tham số ở phạm vi tổng quát.

Khai báo chi tiết cấu hình FastCGI ở các ngữ cảnh cha

Một cách để giảm sự lặp lại là khai báo các chi tiết cấu hình ở một ngữ cảnh cha (parent context). Tất cả các tham số ngoài chỉ thị fastcgi_pass có thể được chỉ định ở mức cao hơn và sẽ tự động áp dụng cho các location bên dưới. Điều này có nghĩa là nhiều location có thể sử dụng cùng một cấu hình.

Ví dụ, chúng ta có thể sửa đổi cấu hình đã nêu ở phần trước để có thể sử dụng cho nhiều location:

# server context
root /var/www/html;

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;

location /scripts {
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
}

. . .

Trong ví dụ trên, các khai báo fastcgi_param và chỉ thị fastcgi_index được áp dụng cho cả hai location sau đó, qua đó loại bỏ sự lặp lại.

Tuy nhiên, cấu hình trên có một bất lợi nghiêm trọng. Nếu bất kỳ tham số fastcgi_param nào được khai báo trong ngữ cảnh con, tất cả các giá trị fastcgi_param từ ngữ cảnh cha sẽ không được thừa hưởng. Bạn hoặc sử dụng các giá trị được thừa hưởng, hoặc không sử dụng chúng.

Chỉ thị fastcgi_param là một chỉ thị dạng mảng trong thuật ngữ của Nginx. Theo quan điểm của người dùng, một chỉ thị mảng có thể được sử dụng nhiều lần trong cùng một ngữ cảnh. Mỗi khai báo bổ sung sẽ được thêm vào bộ sưu tập nội bộ của Nginx cho chỉ thị đó.

Các chỉ thị mảng được thừa hưởng sang các ngữ cảnh con theo cách khác với một số chỉ thị khác. Thông tin từ các chỉ thị mảng sẽ được thừa hưởng sang các ngữ cảnh con chỉ nếu chúng không được khai báo lại trong bất cứ đâu ở ngữ cảnh con. Điều này có nghĩa là nếu bạn sử dụng fastcgi_param trong location, thì nó sẽ xóa toàn bộ các giá trị được thừa hưởng từ ngữ cảnh cha.

Ví dụ, chúng ta có thể sửa đổi cấu hình trên một chút:

# server context
root /var/www/html;

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;

location /scripts {
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
    fastcgi_param QUERY_STRING $query_string;
    fastcgi_pass 127.0.0.1:9000;
}

. . .

Ban đầu, bạn có thể nghĩ rằng các tham số REQUEST_METHODSCRIPT_FILENAME sẽ được thừa hưởng vào khối location thứ hai, và tham số QUERY_STRING sẽ được bổ sung cho ngữ cảnh đó. Tuy nhiên, thực tế là tất cả các giá trị fastcgi_param từ ngữ cảnh cha sẽ bị xóa hoàn toàn trong khối location thứ hai và chỉ có tham số QUERY_STRING được thiết lập. Các tham số REQUEST_METHOD và SCRIPT_FILENAME sẽ không được thiết lập.

Lưu ý về việc khai báo nhiều giá trị cho cùng một tham số trong cùng một ngữ cảnh

Điều cần chú ý ở đây là việc thiết lập nhiều giá trị cho cùng một tham số trong cùng một ngữ cảnh có thể gây ra hậu quả không mong muốn. Hãy cùng xem xét ví dụ sau:

# server context

location ~ \.php$ {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $request_uri;

    fastcgi_param DOCUMENT_ROOT initial;
    fastcgi_param DOCUMENT_ROOT override;

    fastcgi_param TEST one;
    fastcgi_param TEST two;
    fastcgi_param TEST three;

    fastcgi_pass 127.0.0.1:9000;
}

. . .

Trong ví dụ trên, chúng ta đã thiết lập các tham số TESTDOCUMENT_ROOT nhiều lần trong cùng một ngữ cảnh. Vì fastcgi_param là một chỉ thị dạng mảng, nên mỗi lần khai báo sau sẽ được thêm vào bộ lưu trữ nội bộ của Nginx. Tham số TEST sẽ có các giá trị trong mảng lần lượt là one, twothree.

Điều quan trọng cần nhận ra là tất cả các giá trị này sẽ được chuyển tiếp đến backend FastCGI mà không có bất kỳ xử lý bổ sung nào từ Nginx. Điều này có nghĩa là việc xử lý các giá trị đó hoàn toàn phụ thuộc vào FastCGI processor đã chọn. Thật không may, các FastCGI processor khác nhau sẽ xử lý các giá trị này theo cách hoàn toàn khác nhau.

Ví dụ, nếu các tham số trên được PHP-FPM nhận, giá trị cuối cùng sẽ được hiểu là ghi đè lên các giá trị trước đó. Trong trường hợp này, tham số TEST sẽ có giá trị là three. Tương tự, tham số DOCUMENT_ROOT cũng sẽ được đặt thành giá trị ghi đè.

Tuy nhiên, nếu các giá trị trên được chuyển đến một thứ như FsgiWrap, các giá trị sẽ được diễn giải rất khác. Ban đầu, nó sẽ kiểm tra lần đầu để quyết định sử dụng giá trị nào để chạy script – cụ thể, nó sẽ sử dụng giá trị DOCUMENT_ROOT ban đầu (initial) để tìm kiếm script. Tuy nhiên, khi chuyển các tham số thực tế cho script, nó sẽ chuyển các giá trị cuối cùng, giống như PHP-FPM.

Sự không nhất quán và khó lường này có nghĩa là bạn không thể và không nên dựa vào backend để diễn giải đúng ý định của bạn khi thiết lập cùng một tham số nhiều lần. Giải pháp an toàn duy nhất là chỉ khai báo mỗi tham số một lần. Điều này cũng có nghĩa là không có cách nào để ghi đè một giá trị mặc định một cách an toàn bằng chỉ thị fastcgi_param.

Sử dụng include để trích xuất cấu hình FastCGI từ một file riêng

Một cách khác để tách riêng các mục cấu hình chung là sử dụng chỉ thị include để đọc nội dung của một file riêng tại vị trí khai báo chỉ thị.

Điều này có nghĩa là chúng ta có thể giữ tất cả các mục cấu hình chung trong một file duy nhất và include nó vào bất cứ nơi nào trong cấu hình cần sử dụng. Vì Nginx sẽ chèn nội dung file vào chỗ chỉ thị include được gọi, chúng ta sẽ không gặp vấn đề về thừa kế từ ngữ cảnh cha sang ngữ cảnh con. Điều này giúp các giá trị fastcgi_param không bị xóa, cho phép chúng ta thiết lập thêm các tham số cần thiết.

Đầu tiên, chúng ta có thể đặt các giá trị cấu hình FastCGI chung trong một file riêng trong thư mục cấu hình. Chúng ta sẽ gọi file này là fastcgi_common:

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

Bây giờ, chúng ta có thể chèn file này vào bất cứ đâu chúng ta cần sử dụng các giá trị cấu hình chung:

# server context
root /var/www/html;

location /scripts {
    include fastcgi_common;

    fastcgi_index index.php;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
    include fastcgi_common;
    fastcgi_param QUERY_STRING $query_string;
    fastcgi_param CONTENT_TYPE $content_type;
    fastcgi_param CONTENT_LENGTH $content_length;

    fastcgi_index index.php;
    fastcgi_pass 127.0.0.1:9000;
}

. . .

Ở đây, chúng ta đã chuyển một số giá trị fastcgi_param chung sang file fastcgi_common trong thư mục cấu hình mặc định của Nginx. Sau đó, chúng ta chèn file này vào các location cần sử dụng cấu hình chung.

Có một vài điểm cần lưu ý về cấu hình này.

Điều đầu tiên là chúng ta không đặt bất kỳ giá trị nào mà có thể muốn tùy chỉnh theo từng location vào file mà chúng ta định include. Do vấn đề về cách diễn giải đã nêu ở trên khi thiết lập nhiều giá trị cho cùng một tham số, và vì các chỉ thị không phải mảng chỉ có thể được thiết lập một lần trong mỗi ngữ cảnh, chỉ nên đưa vào file chung những mục mà bạn không muốn thay đổi. Mỗi chỉ thị (hoặc khóa tham số) mà bạn có thể muốn tùy chỉnh theo ngữ cảnh nên được loại trừ khỏi file chung.

Điều tiếp theo mà bạn có thể nhận thấy là chúng ta đã thiết lập thêm một số tham số FastCGI trong khối location thứ hai. Đây chính là khả năng mà chúng ta hướng tới: có thể thiết lập thêm các tham số fastcgi_param khi cần thiết mà không làm mất đi các giá trị chung đã được thiết lập trước đó.

Sử dụng file fastcgi_params hoặc fastcgi.conf

Với chiến lược trên, các nhà phát triển Nginx cùng nhiều nhóm đóng gói phân phối đã cùng nhau cung cấp một bộ các tham số chung hợp lý mà bạn có thể include trong các location sử dụng FastCGI pass. Các file này được gọi là fastcgi_params hoặc fastcgi.conf.

Hai file này phần lớn giống nhau, chỉ khác nhau do vấn đề đã thảo luận trước đó về việc truyền nhiều giá trị cho cùng một tham số. File fastcgi_params không chứa khai báo cho tham số SCRIPT_FILENAME, trong khi file fastcgi.conf có chứa.

File fastcgi_params đã tồn tại từ rất lâu. Để tránh phá vỡ các cấu hình dựa vào fastcgi_params, khi quyết định cung cấp một giá trị mặc định cho SCRIPT_FILENAME, một file mới cần được tạo ra. Nếu không, tham số này có thể bị thiết lập đồng thời ở cả file chung và location FastCGI pass. Điều này được trình bày chi tiết trong bài viết xuất sắc của Martin Fjordvald về lịch sử của hai file này.

Nhiều người quản trị gói cho các bản phân phối phổ biến đã chọn chỉ include một trong hai file này hoặc sao chép chính xác nội dung của chúng. Nếu bạn chỉ có một trong số này, hãy sử dụng file có sẵn. Bạn cũng có thể tự do chỉnh sửa nó cho phù hợp với nhu cầu của mình.

Nếu bạn có cả hai file này, đối với hầu hết các location FastCGI pass, có lẽ tốt hơn là include file fastcgi.conf, vì nó bao gồm khai báo cho tham số SCRIPT_FILENAME. Thông thường đây là điều mong muốn, mặc dù có những trường hợp bạn có thể muốn tùy chỉnh giá trị này.

Các file này có thể được include bằng cách tham chiếu đến vị trí của chúng so với thư mục cấu hình gốc của Nginx. Thư mục cấu hình gốc của Nginx thường là /etc/nginx khi Nginx được cài đặt qua trình quản lý gói.

Bạn có thể include các file như sau:

# server context

location ~ \.php$ {
    include fastcgi_params;
    # You would use "fastcgi_param SCRIPT_FILENAME . . ." here afterwards
    
    . . .

}

Hoặc như sau:

# server context

location ~ \.php$ {
    include fastcgi.conf;

    . . .

}

Các chỉ thị, tham số và biến FastCGI quan trọng

Trong các phần trên, chúng ta đã thiết lập khá nhiều tham số, thường được gán với các biến của Nginx, nhằm trình bày một số khái niệm. Chúng ta cũng đã giới thiệu một số chỉ thị FastCGI mà không đi quá sâu vào giải thích. Trong phần này, chúng ta sẽ thảo luận về một số chỉ thị phổ biến cần thiết, các tham số có thể cần tùy chỉnh và một số biến chứa thông tin cần thiết.

Các chỉ thị FastCGI phổ biến

Dưới đây là một số chỉ thị hữu ích khi làm việc với chuyển tiếp FastCGI:

  • fastcgi_pass: Chỉ thị dùng để chuyển tiếp yêu cầu trong ngữ cảnh hiện tại đến máy chủ backend. Nó xác định vị trí mà bộ xử lý FastCGI có thể được truy cập.

  • fastcgi_param: Chỉ thị dạng mảng dùng để thiết lập các tham số cho các giá trị. Thông thường, nó được sử dụng kết hợp với các biến của Nginx để thiết lập các tham số FastCGI dựa trên giá trị cụ thể của yêu cầu.

  • try_files: Không phải là chỉ thị đặc thù của FastCGI, nhưng nó thường được sử dụng trong các location chuyển tiếp FastCGI để kiểm tra tính hợp lệ của file trước khi chuyển tiếp yêu cầu.

  • include: Lại không phải là chỉ thị đặc thù của FastCGI nhưng được sử dụng phổ biến trong các location FastCGI để include các mục cấu hình chung.

  • fastcgi_split_path_info: Chỉ thị này định nghĩa một biểu thức chính quy với hai nhóm được bắt. Nhóm đầu tiên được sử dụng làm giá trị cho biến $fastcgi_script_name. Nhóm thứ hai được sử dụng làm giá trị cho biến $fastcgi_path_info. Cả hai thường được dùng để phân tích đúng yêu cầu, giúp bộ xử lý biết phần nào của yêu cầu là file cần chạy và phần nào là thông tin bổ sung cho script.

  • fastcgi_index: Xác định file index sẽ được thêm vào cuối giá trị của $fastcgi_script_name nếu nó kết thúc bằng dấu gạch chéo (/). Điều này rất hữu ích khi tham số SCRIPT_FILENAME được thiết lập thành $document_root$fastcgi_script_name và khối location được cấu hình để chấp nhận yêu cầu kèm thông tin sau file.

  • fastcgi_intercept_errors: Chỉ thị này xác định liệu các lỗi nhận được từ máy chủ FastCGI có nên được Nginx xử lý hay được truyền trực tiếp cho máy khách.

Những chỉ thị trên đại diện cho phần lớn các thành phần bạn sẽ sử dụng khi thiết kế một chuyển tiếp FastCGI thông thường. Có thể bạn không sử dụng hết tất cả các chỉ thị này mọi lúc, nhưng chúng cho thấy cách chúng tương tác chặt chẽ với các tham số và biến FastCGI sẽ được bàn đến tiếp theo.

Các biến thông dụng khi làm việc với FastCGI

Trước khi bàn về các tham số mà bạn có thể sử dụng với chuyển tiếp FastCGI, hãy cùng điểm qua một số biến Nginx thông dụng mà chúng ta sẽ sử dụng:

  • $query_string hoặc $args: Các tham số được gửi kèm theo yêu cầu gốc của máy khách.
  • $is_args: Sẽ bằng “?” nếu có tham số trong yêu cầu và là chuỗi rỗng nếu không có. Hữu ích khi xây dựng các tham số có thể có hoặc không có tham số.
  • $request_method: Cho biết phương thức yêu cầu gốc từ máy khách. Thông tin này hữu ích để xác định liệu một thao tác có được phép trong ngữ cảnh hiện tại hay không.
  • $content_type: Được thiết lập dựa trên header Content-Type của yêu cầu. Thông tin này cần thiết nếu yêu cầu là POST để xử lý đúng nội dung gửi kèm.
  • $content_length: Được thiết lập dựa trên header Content-Length của máy khách, cần thiết cho bất kỳ yêu cầu POST nào.
  • $fastcgi_script_name: Sẽ chứa tên của file script cần chạy. Nếu yêu cầu kết thúc bằng dấu gạch chéo (/), giá trị của chỉ thị fastcgi_index sẽ được thêm vào. Nếu sử dụng fastcgi_split_path_info, biến này sẽ được thiết lập dựa trên nhóm bắt đầu tiên của biểu thức chính quy.
  • $request_filename: Chứa đường dẫn file của file được yêu cầu, được tính bằng cách kết hợp giá trị của thư mục gốc (theo chỉ thị root hoặc alias) với giá trị của $fastcgi_script_name.
  • $request_uri: Toàn bộ yêu cầu nhận từ máy khách, bao gồm cả tên script, thông tin bổ sung về đường dẫn và các tham số query.
  • $fastcgi_path_info: Chứa thông tin bổ sung về đường dẫn có thể có sau tên script trong yêu cầu. Biến này nhận giá trị từ nhóm thứ hai của chỉ thị fastcgi_split_path_info nếu có.
  • $document_root: Chứa giá trị của thư mục gốc hiện tại, được thiết lập theo chỉ thị root hoặc alias.
  • $uri: Chứa URI hiện tại với các thao tác chuẩn hóa được áp dụng. Vì các chỉ thị rewrite hoặc redirect nội bộ có thể ảnh hưởng đến URI, biến này thể hiện các thay đổi đó.

Như bạn có thể thấy, có rất nhiều biến có sẵn để bạn thiết lập các tham số FastCGI. Nhiều biến có vẻ tương tự nhau nhưng có những khác biệt tinh tế có thể ảnh hưởng đến cách thực thi các script.

Các tham số FastCGI thông dụng

Các tham số FastCGI đại diện cho cặp thông tin khóa-giá trị mà chúng ta muốn truyền đến FastCGI processor nhận yêu cầu. Không phải ứng dụng nào cũng cần các tham số giống nhau, do đó bạn thường phải tham khảo tài liệu của ứng dụng.

Một số tham số này là cần thiết để processor xác định chính xác script cần chạy. Một số khác được cung cấp cho script, có thể thay đổi hành vi của nó nếu được cấu hình để dựa vào các tham số đã thiết lập.

  • QUERY_STRING: Tham số này nên được thiết lập với bất kỳ chuỗi truy vấn nào do client cung cấp. Thông thường, đây là các cặp key-value được cung cấp sau dấu “?” trong URI. Tham số này thường được gán bằng biến $query_string hoặc $args, cả hai đều chứa cùng một dữ liệu.
  • REQUEST_METHOD: Tham số này cho biết FastCGI processor yêu cầu thực hiện loại hành động nào từ phía client. Đây là một trong số ít tham số cần được thiết lập để quá trình pass hoạt động chính xác.
  • CONTENT_TYPE: Nếu phương thức yêu cầu được thiết lập ở trên là “POST”, tham số này phải được thiết lập. Nó cho biết loại nội dung mà FastCGI processor cần mong đợi. Hầu như, tham số này chỉ cần được gán bằng biến $content_type, được thiết lập theo thông tin trong yêu cầu ban đầu.
  • CONTENT_LENGTH: Nếu phương thức yêu cầu là “POST”, tham số này cũng cần được thiết lập. Nó cho biết độ dài của nội dung. Hầu như, tham số này chỉ cần được gán bằng $content_length, biến lấy giá trị từ thông tin trong yêu cầu của client.
  • SCRIPT_NAME: Tham số này được sử dụng để chỉ định tên của script chính sẽ được chạy. Đây là tham số cực kỳ quan trọng và có thể được thiết lập theo nhiều cách khác nhau tùy thuộc vào nhu cầu của bạn. Thông thường, nó được gán bằng $fastcgi_script_name, có thể là URI của yêu cầu, URI của yêu cầu với fastcgi_index được nối vào nếu kết thúc bằng dấu gạch chéo, hoặc nhóm bắt đầu tiên nếu sử dụng fastcgi_fix_path_info.
  • SCRIPT_FILENAME: Tham số này chỉ định vị trí thực sự trên đĩa của script cần chạy. Vì nó có liên quan mật thiết với tham số SCRIPT_NAME, một số hướng dẫn gợi ý bạn sử dụng $document_root$fastcgi_script_name. Một lựa chọn khác có nhiều ưu điểm là sử dụng $request_filename.
  • REQUEST_URI: Tham số này nên chứa toàn bộ URI của yêu cầu, không bị thay đổi, bao gồm cả tên script cần chạy, thông tin bổ sung của path và bất kỳ đối số nào. Một số ứng dụng thích tự phân tích thông tin này. Tham số này cung cấp cho chúng thông tin cần thiết để thực hiện việc đó.
  • PATH_INFO: Nếu cài đặt cgi.fix_pathinfo được đặt thành “1” trong file cấu hình PHP, tham số này sẽ chứa bất kỳ thông tin bổ sung nào được thêm sau tên script. Thông thường, nó được dùng để xác định đối số file mà script cần xử lý. Việc đặt cgi.fix_pathinfo thành “1” có thể gây ra các vấn đề bảo mật nếu các yêu cầu của script không được kiểm tra kỹ càng bằng các biện pháp khác (chúng ta sẽ bàn về vấn đề này sau). Đôi khi, nó được gán bằng biến $fastcgi_path_info, chứa nhóm bắt thứ hai từ chỉ thị fastcgi_split_path_info. Trong những trường hợp khác, có thể cần sử dụng một biến tạm thời vì giá trị này đôi khi bị ghi đè bởi các xử lý khác.
  • PATH_TRANSLATED: Tham số này ánh xạ thông tin path có trong PATH_INFO thành một đường dẫn thực tế trên hệ thống file. Thông thường, nó sẽ được thiết lập thành $document_root$fastcgi_path_info, nhưng đôi khi biến $fastcgi_path_info sau đó cần được thay thế bằng biến tạm thời như đã nêu ở trên.
 Kiểm tra các yêu cầu trước khi chuyển tiếp sang FastCGI

Một chủ đề rất quan trọng mà chúng ta chưa bàn tới là cách an toàn khi chuyển tiếp các yêu cầu động đến máy chủ ứng dụng. Việc chuyển tất cả yêu cầu đến backend mà không kiểm tra tính hợp lệ không chỉ kém hiệu quả mà còn nguy hiểm, vì kẻ tấn công có thể gửi các yêu cầu độc hại nhằm cố gắng làm cho máy chủ chạy mã tùy ý.

Để giải quyết vấn đề này, chúng ta cần đảm bảo chỉ chuyển các yêu cầu hợp lệ đến bộ xử lý FastCGI. Có nhiều cách khác nhau để làm điều này, tùy thuộc vào nhu cầu của hệ thống và việc bộ xử lý FastCGI có chạy cùng máy với Nginx hay không.

Một nguyên tắc cơ bản là không cho phép xử lý và giải mã các file do người dùng tải lên. Vì rất dễ để người dùng ác ý chèn mã hợp lệ vào các file tưởng chừng vô hại, như hình ảnh. Một khi file đó được tải lên máy chủ, chúng ta cần đảm bảo rằng nó không bao giờ được chuyển tới bộ xử lý FastCGI.

Vấn đề lớn mà chúng ta muốn giải quyết ở đây được nêu rõ trong tiêu chuẩn CGI. Tiêu chuẩn cho phép chỉ định một file script để chạy, theo sau là thông tin đường dẫn bổ sung mà script có thể sử dụng. Mô hình thực thi này cho phép người dùng yêu cầu một URI trông có vẻ như là một script hợp lệ, trong khi phần thực sự sẽ được thực thi lại là phần trước đó trong đường dẫn.

Hãy xem xét yêu cầu cho /test.jpg/index.php. Nếu cấu hình của bạn chuyển tiếp mọi yêu cầu kết thúc bằng .php đến bộ xử lý mà không kiểm tra tính hợp lệ, bộ xử lý (nếu theo tiêu chuẩn CGI) sẽ tìm file đó và thực thi nó nếu có thể. Nếu không tìm thấy, nó sẽ theo tiêu chuẩn và thử thực thi file /test.jpg, gán /index.php là thông tin đường dẫn bổ sung cho script. Như bạn thấy, điều này có thể dẫn đến hậu quả không mong muốn khi kết hợp với ý tưởng cho phép người dùng tải lên file.

Có nhiều cách khác nhau để giải quyết vấn đề này. Cách đơn giản nhất, nếu ứng dụng của bạn không phụ thuộc vào thông tin đường dẫn bổ sung, là vô hiệu hóa tính năng này trong bộ xử lý. Đối với PHP-FPM, bạn có thể vô hiệu hóa tính năng này trong file php.ini. Ví dụ, trên hệ thống Ubuntu, bạn có thể chỉnh sửa file:

sudo nano /etc/php5/fpm/php.ini

Sau đó tìm kiếm tùy chọn cgi.fix_pathinfo, bỏ comment và đặt giá trị thành “0” để vô hiệu hóa:

cgi.fix_pathinfo=0

Khởi động lại dịch vụ PHP-FPM để áp dụng thay đổi:

sudo service php5-fpm restart

Việc này sẽ khiến PHP chỉ cố gắng thực thi thành phần cuối cùng của đường dẫn. Trong ví dụ trên, nếu file /test.jpg/index.php không tồn tại, PHP sẽ báo lỗi thay vì cố gắng thực thi file /test.jpg.

Một lựa chọn khác, nếu bộ xử lý FastCGI chạy trên cùng máy với Nginx, là kiểm tra sự tồn tại của file trên đĩa trước khi chuyển tiếp yêu cầu. Nếu file /test.jpg/index.php không tồn tại, trả về lỗi 404; nếu có, chuyển tiếp yêu cầu đến backend để xử lý. Thực tế, cách này sẽ mang lại hành vi tương tự như đã mô tả:

location ~ \.php$ {
        try_files $uri =404;

        . . .

}

Nếu ứng dụng của bạn phụ thuộc vào hành vi thông tin đường dẫn bổ sung để giải mã yêu cầu, bạn vẫn có thể cho phép hành vi này một cách an toàn bằng cách kiểm tra cẩn thận trước khi chuyển tiếp yêu cầu đến backend.

Ví dụ, bạn có thể xác định rõ các thư mục cho phép tải lên không đáng tin cậy và đảm bảo rằng chúng không được chuyển tiếp đến bộ xử lý. Giả sử thư mục tải lên của ứng dụng là /uploads/, bạn có thể tạo một khối location khớp trước các biểu thức chính quy:

location ^~ /uploads {
}

Bên trong, bạn có thể vô hiệu hóa xử lý các file PHP:

location ^~ /uploads {
    location ~* \.php$ { return 403; }
}

Khối location cha sẽ khớp với bất kỳ yêu cầu nào bắt đầu bằng /uploads và yêu cầu liên quan đến file PHP sẽ trả về lỗi 403 thay vì chuyển tiếp đến backend.

Bạn cũng có thể sử dụng chỉ thị fastcgi_split_path_info để xác định rõ phần nào của yêu cầu là script và phần nào là thông tin đường dẫn bổ sung bằng cách sử dụng biểu thức chính quy. Điều này cho phép bạn vẫn giữ được chức năng của thông tin đường dẫn bổ sung, nhưng chỉ định chính xác phần nào là script và phần nào là thông tin bổ sung.

Ví dụ, ta có thể thiết lập một khối location mà tại đó phần đầu tiên của đường dẫn kết thúc bằng .php được coi là script cần chạy, phần còn lại được coi là thông tin bổ sung:

location ~ [^/]\.php(/|$) {

    fastcgi_split_path_info ^(.+?\.php)(.*)$;
    set $orig_path $fastcgi_path_info;

    try_files $fastcgi_script_name =404;

    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;

    fastcgi_param SCRIPT_FILENAME $request_filename;
    fastcgi_param PATH_INFO $orig_path;
    fastcgi_param PATH_TRANSLATED $document_root$orig_path;
}

Khối trên hoạt động với cấu hình PHP có cài đặt cgi.fix_pathinfo thành “1” để cho phép thông tin đường dẫn bổ sung. Location trên không chỉ khớp với các yêu cầu kết thúc bằng .php, mà còn khớp với các yêu cầu có .php ngay trước dấu gạch chéo (/), biểu thị có thêm một thành phần đường dẫn sau đó. Bên trong khối, chỉ thị fastcgi_split_path_info định nghĩa hai nhóm bắt thông qua biểu thức chính quy. Nhóm thứ nhất chứa phần của URI từ đầu đến phần đầu tiên có .php và được gán cho biến $fastcgi_script_name. Phần còn lại được gán cho biến $fastcgi_path_info.

Chúng ta sử dụng chỉ thị set để lưu giá trị của $fastcgi_path_info vào biến tạm $orig_path, bởi vì sau đó biến $fastcgi_path_info sẽ bị xóa do chỉ thị try_files.

Chỉ thị try_files được sử dụng để kiểm tra xem file script được xác định có tồn tại trên đĩa hay không. Sau đó, sau khi thực hiện chuyển tiếp FastCGI, chúng ta thiết lập SCRIPT_FILENAME như thường lệ, đồng thời thiết lập PATH_INFO với giá trị từ biến $orig_pathPATH_TRANSLATED bằng cách kết hợp $document_root với $orig_path.

Cấu hình này cho phép tạo ra các yêu cầu như /index.php/users/view để file /index.php xử lý thông tin về thư mục /users/view, đồng thời tránh trường hợp /test.jpg/index.php được thực thi. Nó luôn thiết lập script là phần ngắn nhất kết thúc bằng .php, do đó tránh được vấn đề trên.

Ta thậm chí có thể làm việc này với chỉ thị alias nếu cần thay đổi vị trí file script, chỉ cần điều chỉnh ở cả phần header location và định nghĩa fastcgi_split_path_info:

location ~ /test/.+[^/]\.php(/|$) {

    alias /var/www/html;

    fastcgi_split_path_info ^/test(.+?\.php)(.*)$;
    set $orig_path $fastcgi_path_info;

    try_files $fastcgi_script_name =404;

    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;

    fastcgi_param SCRIPT_FILENAME $request_filename;
    fastcgi_param PATH_INFO $orig_path;
    fastcgi_param PATH_TRANSLATED $document_root$orig_path;
}

Cấu hình trên cho phép bạn chạy các ứng dụng sử dụng tham số PATH_INFO một cách an toàn. Lưu ý rằng bạn cần thay đổi tùy chọn cgi.fix_pathinfo trong file php.ini thành “1” để cấu hình này hoạt động đúng. Ngoài ra, có thể bạn cần tắt tùy chọn security.limit_extensions trong file cấu hình php-fpm.conf.

Kết luận

Hy vọng rằng qua bài viết này, bạn đã hiểu rõ hơn về khả năng proxy FastCGI của Nginx. Tính năng này giúp Nginx phát huy ưu thế trong việc xử lý các kết nối nhanh và phục vụ các nội dung tĩnh, đồng thời chuyển giao công việc xử lý nội dung động cho các phần mềm chuyên biệt hơn.

FastCGI giúp Nginx có thể tích hợp và làm việc hiệu quả với nhiều ứng dụng khác nhau, đồng thời đảm bảo các cấu hình hoạt động với hiệu suất cao và bảo mật chặt chẽ. Khi tối ưu FastCGI Proxying, việc thuê máy chủ VPS phù hợp sẽ nâng cao hiệu quả xử lý. Máy chủ VPS với cấu hình mạnh, băng thông lớn và bảo mật cao giúp Nginx vận hành trơn tru, giảm thời gian tải trang, mang lại lợi thế cạnh tranh cho website của bạn.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *