>  기사  >  백엔드 개발  >  PHP 코어 - SAPI 인터페이스

PHP 코어 - SAPI 인터페이스

伊谢尔伦
伊谢尔伦원래의
2016-11-21 16:41:271844검색

SAPI: 서버 애플리케이션 프로그래밍 인터페이스 서버측 애플리케이션 프로그래밍 포트입니다. PHP 아키텍처를 공부한 학생들은 PHP가 다른 애플리케이션과 상호 작용할 수 있도록 하는 인터페이스를 제공하는 것의 중요성을 알아야 합니다. 이 기사에서는 각 PHP SAPI를 자세히 소개하지 않고 가장 간단한 CGI SAPI에 대한 SAPI 메커니즘만 설명합니다.

먼저 PHP의 아키텍처 다이어그램을 살펴보겠습니다.

PHP 코어 - SAPI 인터페이스

SAPI는 PC와 마찬가지로 특정 PHP 애플리케이션을 위한 프로그래밍 인터페이스를 의미합니다. 설치된 항목이 무엇이든 운영 체제가 PC 인터페이스 사양을 충족하는 한 PC에서 정상적으로 실행될 수 있습니다. 웹 서버를 통해, 명령줄에서 직접 실행하거나 다른 프로그램에 포함된 방식으로 PHP 스크립트를 실행할 수 있습니다.

보통 우리는 Apache나 Nginx와 같은 웹 서버를 사용하여 PHP 스크립트를 테스트하거나 명령줄에서 PHP 인터프리터 프로그램을 통해 실행합니다. 스크립트가 실행된 후 웹 서버가 응답하고 브라우저는 응답 정보를 표시하거나 명령줄의 표준 출력에 내용을 표시합니다.

우리는 PHP 인터프리터가 어디에 있는지 거의 신경 쓰지 않습니다. 웹 서버와 명령줄 프로그램을 통해 스크립트를 실행하는 것은 매우 다르게 보이지만 작업 흐름은 실제로 동일합니다. 명령줄 매개변수는 PHP 인터프리터에 의해 실행될 스크립트로 전달됩니다. 이는 URL을 통해 PHP 페이지를 요청하는 것과 같습니다. 스크립트가 실행된 후 응답 결과가 반환되지만 터미널에는 명령줄의 응답 결과가 표시됩니다.

스크립트 실행은 SAPI 인터페이스 구현으로 시작됩니다. 단지 다른 SAPI 인터페이스 구현이 특정 작업을 완료한다는 것뿐입니다. 예를 들어 Apache의 mod_php SAPI 구현은 Apache에서 얻은 일부 정보를 초기화하고 콘텐츠를 출력할 때 해당 콘텐츠를 Apache에 반환해야 합니다.

SAPI는 외부 세계와 통신하기 위한 인터페이스를 제공합니다. PHP5.2의 경우 Apache, CGI, IIS용 ISAPI 및 Shell CLI에 대한 다양한 종류의 SAPI가 기본적으로 제공됩니다. CGI SAPI로 시작하여 SAPI의 메커니즘을 소개합니다. CGI는 간단하지만 걱정하지 마세요. 여기에는 SAPI 작동 방식을 깊이 이해할 수 있는 대부분의 콘텐츠가 포함되어 있습니다.

SAPI를 정의하려면 먼저 sapi_module_struct를 정의하고 PHP-SRC/sapi/cgi/cgi_main.c를 확인해야 합니다.

*/
static sapi_module_struct cgi_sapi_module = {
#if PHP_FASTCGI
    "cgi-fcgi",                     /* name */
    "CGI/FastCGI",                  /* pretty name */
#else
    "cgi",                          /* name */
    "CGI",                          /* pretty name */
#endif
    php_cgi_startup,                /* startup */
    php_module_shutdown_wrapper,    /* shutdown */
    NULL,                           /* activate */
    sapi_cgi_deactivate,            /* deactivate */
    sapi_cgibin_ub_write,           /* unbuffered write */
    sapi_cgibin_flush,              /* flush */
    NULL,                           /* get uid */
    sapi_cgibin_getenv,             /* getenv */
    php_error,                      /* error handler */
    NULL,                           /* header handler */
    sapi_cgi_send_headers,          /* send headers handler */
    NULL,                           /* send header handler */
    sapi_cgi_read_post,             /* read POST data */
    sapi_cgi_read_cookies,          /* read Cookies */
    sapi_cgi_register_variables,    /* register server variables */
    sapi_cgi_log_message,           /* Log message */
    NULL,                           /* Get request time */
    STANDARD_SAPI_MODULE_PROPERTIES
};

이 구조에는 이름, 이는 php_info()를 호출할 때 사용됩니다. 일부 초기화, 닫기 함수 및 일부 함수 포인터는 Zend에게 데이터를 얻고 출력하는 방법을 알려주는 데 사용됩니다.

1. php_cgi_startup, 애플리케이션이 PHP를 호출하면 이 함수가 호출됩니다. CGI의 경우 단순히 PHP의 초기화 함수를 호출합니다.

static int php_cgi_startup(sapi_module_struct *sapi_module)
{
    if (php_module_startup(sapi_module, NULL, 0) == FAILURE) {
        return FAILURE;
    }
    return SUCCESS;
}

2. PHP용 간단한 래퍼인 php_module_shutdown_wrapper 종료 기능. 간단히 php_module_shutdown을 호출하세요.

3. PHP는 각 요청에서 일부 초기화 및 리소스 할당 트랜잭션을 처리합니다. 이 부분은 활성화 필드가 정의되는 부분입니다. 위 구조에서 CGI의 경우 초기화 핸들을 제공하지 않는다는 것을 알 수 있습니다. mod_php5의 경우에는 아파치 풀에 리소스 소멸자를 등록하고, 공간을 신청하고, 환경 변수를 초기화해야 합니다.

4. sapi_cgi_deactivate는 activate에 해당하는 함수입니다. 이름에서 알 수 있듯이 CGI의 경우 사용자가 모든 출력을 가져오도록 버퍼를 새로 고칩니다. Zend가 닫히기 전의 데이터:

static int sapi_cgi_deactivate(TSRMLS_D)
{
    /* flush only when SAPI was started. The reasons are:
        1. SAPI Deactivate is called from two places: module init and request shutdown
        2. When the first call occurs and the request is not set up, flush fails on
            FastCGI.
    */
    if (SG(sapi_started)) {
        sapi_cgibin_flush(SG(server_context));
    }
    return SUCCESS;
}

5. sapi_cgibin_ub_write, 이 핸들러는 Zend에 데이터 출력 방법을 알려줍니다. mod_php5의 경우 이 함수는 응답 데이터를 쓰기 위한 인터페이스를 제공하고 CGI의 경우 간단히 작성됩니다. stdout:

static inline size_t sapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC)
{
#ifdef PHP_WRITE_STDOUT
    long ret;
#else
    size_t ret;
#endif
#if PHP_FASTCGI
    if (fcgi_is_fastcgi()) {
        fcgi_request *request = (fcgi_request*) SG(server_context);
        long ret = fcgi_write(request, FCGI_STDOUT, str, str_length);
        if (ret <= 0) {
            return 0;
        }
        return ret;
    }
#endif
#ifdef PHP_WRITE_STDOUT
    ret = write(STDOUT_FILENO, str, str_length);
    if (ret <= 0) return 0;
    return ret;
#else
    ret = fwrite(str, 1, MIN(str_length, 16384), stdout);
    return ret;
#endif
}
static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC)
{
    const char *ptr = str;
    uint remaining = str_length;
    size_t ret;
    while (remaining > 0) {
        ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);
        if (!ret) {
            php_handle_aborted_connection();
            return str_length - remaining;
        }
        ptr += ret;
        remaining -= ret;
    }
    return str_length;
}

fastcgi와 호환되는 쓰기 방법을 간단히 구현하기 위해 실제 쓰기 로직을 ​​제거했습니다.

6. sapi_cgibin_flush는 캐시를 새로 고치기 위해 zend에 제공되는 함수 핸들입니다. CGI의 경우 시스템에서 제공하는 fflush에 대한 간단한 호출입니다. 부분이 사용됩니다. Zend는 실행될 스크립트 파일의 상태를 확인하여 해당 파일에 실행 권한 등이 있는지 확인할 수 있습니다. CGI는 이를 제공하지 않습니다.

8. sapi_cgibin_getenv는 이름을 기반으로 환경 변수를 찾을 수 있는 인터페이스를 Zend에 제공합니다. mod_php5의 경우 스크립트에서 getenv를 호출하면 이 핸들이 간접적으로 호출됩니다. CGI의 경우 작동 메커니즘이 CLI와 매우 유사하기 때문에 직접 호출 상위는 Shell이므로 단순히 시스템에서 제공하는 genenv를 호출합니다.

9 php_error, 오류 처리 기능, 여기서는 몇 가지 여담을 만들어 보겠습니다. 지난번에 PHP 메일리스트를 봤을 때 저는 PHP의 오류 처리 메커니즘을 완전히 OO로 만들었습니다. 즉, 오류가 발생할 때마다 비정상적인 오류 메시지가 표시되도록 이 함수 핸들을 다시 작성했습니다. CGI는 단순히 PHP가 제공하는 오류 처리 기능을 호출합니다.
static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC)
{
#if PHP_FASTCGI
    /* when php is started by mod_fastcgi, no regular environment
       is provided to PHP.  It is always sent to PHP at the start
       of a request.  So we have to do our own lookup to get env
       vars.  This could probably be faster somehow.  */
    if (fcgi_is_fastcgi()) {
        fcgi_request *request = (fcgi_request*) SG(server_context);
        return fcgi_getenv(request, name, name_len);
    }
#endif
    /*  if cgi, or fastcgi and not found in fcgi env
        check the regular environment */
    return getenv(name);
}

10. CGI에서는 제공되지 않는 PHP의 header() 함수를 호출할 때 이 함수가 호출됩니다.

11. sapi_cgi_send_headers, 이 함수는 헤더가 실제로 전송될 때, 일반적으로 출력이 전송되기 전에 호출됩니다.

12. CGI에서는 제공되지 않는 각 헤더를 별도로 제공합니다
static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
    char buf[SAPI_CGI_MAX_HEADER_LENGTH];
    sapi_header_struct *h;
    zend_llist_position pos;
    if (SG(request_info).no_headers == 1) {
        return  SAPI_HEADER_SENT_SUCCESSFULLY;
    }
    if (cgi_nph || SG(sapi_headers).http_response_code != 200)
    {
        int len;
        if (rfc2616_headers && SG(sapi_headers).http_status_line) {
            len = snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH,
                           "%s\r\n", SG(sapi_headers).http_status_line);
            if (len > SAPI_CGI_MAX_HEADER_LENGTH) {
                len = SAPI_CGI_MAX_HEADER_LENGTH;
            }
        } else {
            len = sprintf(buf, "Status: %d\r\n", SG(sapi_headers).http_response_code);
        }
        PHPWRITE_H(buf, len);
    }
    h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
    while (h) {
        /* prevent CRLFCRLF */
        if (h->header_len) {
            PHPWRITE_H(h->header, h->header_len);
            PHPWRITE_H("\r\n", 2);
        }
        h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
    }
    PHPWRITE_H("\r\n", 2);
    return SAPI_HEADER_SENT_SUCCESSFULLY;
   }

13. sapi_cgi_read_post, 这个句柄指明了如何获取POST的数据,如果做过CGI编程的话,我们就知道CGI是从stdin中读取POST DATA的:

static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
{
    uint read_bytes=0, tmp_read_bytes;
#if PHP_FASTCGI
    char *pos = buffer;
#endif
    count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes));
    while (read_bytes < count_bytes) {
#if PHP_FASTCGI
        if (fcgi_is_fastcgi()) {
            fcgi_request *request = (fcgi_request*) SG(server_context);
            tmp_read_bytes = fcgi_read(request, pos, count_bytes - read_bytes);
            pos += tmp_read_bytes;
        } else {
            tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
        }
#else
        tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
#endif
        if (tmp_read_bytes <= 0) {
            break;
        }
        read_bytes += tmp_read_bytes;
    }
    return read_bytes;
}

14. sapi_cgi_read_cookies, 这个和上面的函数一样,只不过是去获取cookie值:

static char *sapi_cgi_read_cookies(TSRMLS_D)
{
   return sapi_cgibin_getenv((char *) "HTTP_COOKIE", sizeof("HTTP_COOKIE")-1 TSRMLS_CC);
}
15. sapi_cgi_register_variables, 这个函数给了一个接口,用以给$_SERVER变量中添加变量,对于CGI来说,注册了一个PHP_SELF,这样我们就可以在脚本中访问$_SERVER[&#39;PHP_SELF&#39;]来获取本次的request_uri:
static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
{
   /* In CGI mode, we consider the environment to be a part of the server
    * variables
    */
   php_import_environment_variables(track_vars_array TSRMLS_CC);
   /* Build the special-case PHP_SELF variable for the CGI version */
   php_register_variable("PHP_SELF", (SG(request_info).request_uri ? SG(request_info).request_uri : ""), track_vars_array TSRMLS_CC);
}
16. sapi_cgi_log_message ,用来输出错误信息,对于CGI来说,只是简单的输出到stderr:
static void sapi_cgi_log_message(char *message)
{
#if PHP_FASTCGI
   if (fcgi_is_fastcgi() && fcgi_logging) {
       fcgi_request *request;
       TSRMLS_FETCH();

       request = (fcgi_request*) SG(server_context);
       if (request) {
           int len = strlen(message);
           char *buf = malloc(len+2);

           memcpy(buf, message, len);
           memcpy(buf + len, "\n", sizeof("\n"));
           fcgi_write(request, FCGI_STDERR, buf, len+1);
           free(buf);
       } else {
           fprintf(stderr, "%s\n", message);
       }
       /* ignore return code */
   } else
#endif /* PHP_FASTCGI */
   fprintf(stderr, "%s\n", message);
}

经过分析,我们已经了解了一个SAPI是如何实现的了, 分析过CGI以后,我们也就可以想象mod_php5, embed等SAPI的实现机制。


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.