<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                第十七章 CGI腳本 ================ <font color="red">(警告:缺乏適當安全防護措施的CGI腳本可能會讓您的網站陷入危險狀態。本文中的腳本只是簡單的樣例而不保證在真實網站使用是安全的。)</font> CGI腳本是駐留在Web服務器上的腳本,而且可以被客戶端(瀏覽器)運行。客戶端通過腳本的URL來訪問腳本,就像訪問普通頁面一樣。服務器識別出請求的URL是一個腳本,于是就運行該腳本。服務器如何識別特定的URL為腳本取決于服務器的管理員。在本文中我們假設腳本都存放在一個單獨的文件夾,名為cgi-bin。因此,www.foo.org網站上的`testcgi.scm`腳本可以通過 http://www.foo.org/cgi-bin/testcgi.scm 來訪問。 服務器以`nobody`用戶的身份來運行腳本,不應當期望這個用戶有`PATH`的環境變量或者該變量正確設置(這太主觀了)。因此用Scheme編寫的腳本的“引導行”會比我們在一般Scheme腳本中更加清楚才行。也就是說,下面這行代碼: ```shell ":";exec mzscheme -r $0 "$@" ``` 隱式的假設有一個特定的shell(如bash),而且設置好了`PATH`變量,而mzscheme程序在PATH的路徑里。對于CGI腳本,我們需要多寫一些: ```shell #!/bin/sh ":";exec /usr/local/bin/mzscheme -r $0 "$@" ``` 這樣指定了shell和Scheme可執行文件的絕對路徑。控制從shell交接給Scheme的過程和普通腳本一致。 ## 17.1 例:顯示環境變量 下面是一個Scheme編寫的CGI腳本的示例,`testcgi.scm`。該文件會輸出一些常用CGI環境變量的設置。這些信息作為一個新的,剛剛創建的頁面返回給瀏覽器。返回的頁面就是該CGI腳本向標準輸出里寫入的任何東西。這就是CGI腳本如何回應對它們的調用——通過返回給它們(客戶端)一個新頁面。 注意腳本首先輸出下面這行: ``` content-type: text/plain ``` 后面跟一個空行。這是Web服務器提供頁面服務的標準方式。這兩行不會在頁面上顯示出來。它們只是提醒瀏覽器下面將發送的頁面是純文本(也就是非標記)文字。這樣瀏覽器就會恰當的顯示這個頁面了。如果我們要發送的頁面是用HTML標記的,`content-type`就是`text/html`。 下面是腳本`testcgi.scm`: ```scheme #!/bin/sh ":";exec /usr/local/bin/mzscheme -r $0 "$@" ;Identify content-type as plain text. (display "content-type: text/plain") (newline) (newline) ;Generate a page with the requested info. This is ;done by simply writing to standard output. (for-each (lambda (env-var) (display env-var) (display " = ") (display (or (getenv env-var) "")) (newline)) '("AUTH_TYPE" "CONTENT_LENGTH" "CONTENT_TYPE" "DOCUMENT_ROOT" "GATEWAY_INTERFACE" "HTTP_ACCEPT" "HTTP_REFERER" ; [sic] "HTTP_USER_AGENT" "PATH_INFO" "PATH_TRANSLATED" "QUERY_STRING" "REMOTE_ADDR" "REMOTE_HOST" "REMOTE_IDENT" "REMOTE_USER" "REQUEST_METHOD" "SCRIPT_NAME" "SERVER_NAME" "SERVER_PORT" "SERVER_PROTOCOL" "SERVER_SOFTWARE")) ``` `testcgi.scm`可以直接從瀏覽器上打開,URL是: http://www.foo.org/cgi-bin/testcgi.scm 此外,`testcgi.scm`也可以放在HTML文件的鏈接中,這樣可以直接點擊,如: ```html ... To view some common CGI environment variables, click <a href="http://www.foo.org/cgi-bin/testcgi.scm">here</a>. ... ``` 而一旦觸發了`testcg.scm`,它就會生成一個包括環境變量設置的純文本頁面。下面是一個示例輸出: ``` AUTH_TYPE = CONTENT_LENGTH = CONTENT_TYPE = DOCUMENT_ROOT = /home/httpd/html GATEWAY_INTERFACE = CGI/1.1 HTTP_ACCEPT = image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* HTTP_REFERER = HTTP_USER_AGENT = Mozilla/3.01Gold (X11; I; Linux 2.0.32 i586) PATH_INFO = PATH_TRANSLATED = QUERY_STRING = REMOTE_HOST = 127.0.0.1 REMOTE_ADDR = 127.0.0.1 REMOTE_IDENT = REMOTE_USER = REQUEST_METHOD = GET SCRIPT_NAME = /cgi-bin/testcgi.scm SERVER_NAME = localhost.localdomain SERVER_PORT = 80 SERVER_PROTOCOL = HTTP/1.0 SERVER_SOFTWARE = Apache/1.2.4 ``` ## 17.2 示例:顯示選擇的環境變量 `testcgi.scm`沒有從用戶獲得任何輸入。一個更專注的腳本會從用戶那里獲得一個環境變量,然后輸出這個變量的設置,此外不返回任何東西。為了做這個,我們需要一個機制把參數傳遞給CGI腳本。HTML的表單提供了這種功能。下面是完成這個目標的一個簡單的HTML頁面: ```html <html> <head> <title>Form for checking environment variables</title> </head> <body> <form method=get action="http://www.foo.org/cgi-bin/testcgi2.scm"> Enter environment variable: <input type=text name=envvar size=30> <p> <input type=submit> </form> </body> </html> ``` 用戶在文本框中輸入希望的環境變量(如`GATEWAY_INTERFACE`)并點擊提交按鈕。這會把所有表單里的信息——這里,參數`envvar`的值是`GATEWAY_INTERFACE`——收集并發送到該表單對應的CGI腳本即`testcgi2.scm`。這些信息可以用兩種方法來發送: 1. 如果表單的`method`屬性是`GET`(默認),那么這些信息通過環境變量`QUERY_STRING`來傳遞給腳本 2. 如果表單的`method`屬性是`POST`,那么這些信息會在稍后發送到CGI腳本的標準輸入中。 我們的表單使用`QUERY_STRING`的方式。 把信息從`QUERY_STRING`中提取出來并輸出相應的頁面是`testcgi2.scm`腳本的事情。 發給CGI腳本的信息,不論通過環境變量還是通過標準輸入,都被格式化為一串“參數/值”的鍵值對。鍵值對之間用`&`字符分隔開。每個鍵值對中參數的名字在前面而且與參數值之間用`=`分開。這種情況下,只有一個鍵值對,即`envvar=GATEWAY_INTERFACE`。 下面是`testcgi2.scm`腳本: ```scheme #!/bin/sh ":";exec /usr/local/bin/mzscheme -r $0 "$@" (display "content-type: text/plain") (newline) (newline) ;string-index returns the leftmost index in string s ;that has character c (define string-index (lambda (s c) (let ((n (string-length s))) (let loop ((i 0)) (cond ((>= i n) #f) ((char=? (string-ref s i) c) i) (else (loop (+ i 1)))))))) ;split breaks string s into substrings separated by character c (define split (lambda (c s) (let loop ((s s)) (if (string=? s "") '() (let ((i (string-index s c))) (if i (cons (substring s 0 i) (loop (substring s (+ i 1) (string-length s)))) (list s))))))) (define args (map (lambda (par-arg) (split #\= par-arg)) (split #\& (getenv "QUERY_STRING")))) (define envvar (cadr (assoc "envvar" args))) (display envvar) (display " = ") (display (getenv envvar)) (newline) ``` 注意輔助過程`split`把`QUERY_STRING`用`&`分隔為鍵值對并進一步用`=`把參數名和參數值分開。(如果我們是用`POST`方法,我們需要把參數名和參數值從標準輸入中提取出來。) `<input type=text>`和`<input type=submit>`是HTML表單的兩個不同的輸入標簽。參考文獻27來查看全部。 ## 17.3 CGI腳本相關問題(utilities) 在上面的例子中,參數名和參數值都假設沒有包含`=`或`&`字符。通常情況他們會包含。為了適應這種字符,而不會不小心把他們當成分割符,CGI參數傳遞機制要求所有除了字母、數字和下劃線以外的“特殊”字符都要編碼進行傳輸。空格被編碼為`+`,其他的特殊字符被編碼為3個字符的序列,包括一個`%`字符緊跟著這個字符的16進制碼。因此,`20% + 30% = 50%, &c.`會被編碼為: ``` 20%25+%2b+30%25+%3d+50%25%2c+%26c%2e ``` (空格變成`+`;`%`變為`%25`;`+`變為`%2b`;`=`變為`%3d`;`,`變為`%2c`;`&`變為`%26`;`.`變為`%2e`) 除了把獲得和解碼表單的代碼寫在每個CGI腳本中,把這些函數放在一個庫文件`cgi.scm`中。這樣`testcgi2.scm`的代碼寫起來更緊湊: ```scheme #!/bin/sh ":";exec /usr/local/bin/mzscheme -r $0 "$@" ;Load the cgi utilities (load-relatve "cgi.scm") (display "content-type: text/plain") (newline) (newline) ;Read the data input via the form (parse-form-data) ;Get the envvar parameter (define envvar (form-data-get/1 "envvar")) ;Display the value of the envvar (display envvar) (display " = ") (display (getenv envvar)) (newline) ``` 這個簡短一些的CGI腳本用了兩個定義在`cgi.scm`中的通用過程。`parse-form-data`過程讀取用戶通過表單提交的數據,包括參數和值。 `form-data-get/1`找到與特定參數關聯的值。 `cgi.scm`定義了一個全局表叫`*form-data-table*`來存放表單數據。 ```scheme ;Load our table definitions (load-relative "table.scm") ;Define the *form-data-table* (define *form-data-table* (make-table 'equ string=?)) ``` 使用諸如`parse-form-data`等通用過程的一個好處是我們可以不用管用戶是用那種方式(get或post)提交的數據。 ```scheme (define parse-form-data (lambda () ((if (string-ci=? (or (getenv "REQUEST_METHOD") "GET") "GET") parse-form-data-using-query-string parse-form-data-using-stdin)))) ``` 環境變量`REQUEST_METHOD`表示使用那種方式傳送表單數據。如果方法是`GET`,那么表單數據被作為字符串通過另一個環境變量`QUERY_STRING`傳輸。輔助過程`parse-form-data-using-query-string`用來拆散`QUERY_STRING`: ```scheme (define parse-form-data-using-query-string (lambda () (let ((query-string (or (getenv "QUERY_STRING") ""))) (for-each (lambda (par=arg) (let ((par/arg (split #\= par=arg))) (let ((par (url-decode (car par/arg))) (arg (url-decode (cadr par/arg)))) (table-put! *form-data-table* par (cons arg (table-get *form-data-table* par '())))))) (split #\& query-string))))) ``` 輔助過程`split`,和它的輔助過程`string-index`,在第二節中定義過了。正如之前提到的,輸入的表單數據是一串用`&`分割的鍵值對。每個鍵值對中先是參數名,然后是一個`=`號,后面是值。每個鍵值對都放到一個全局的表`*form-data-table*`里。 每個參數名和參數值都被編碼了,所以我們需要用`url-decode`過程來解碼得到它們的真實表示。 ```scheme (define url-decode (lambda (s) (let ((s (string->list s))) (list->string (let loop ((s s)) (if (null? s) '() (let ((a (car s)) (d (cdr s))) (case a ((#\+) (cons #\space (loop d))) ((#\%) (cons (hex->char (car d) (cadr d)) (loop (cddr d)))) (else (cons a (loop d))))))))))) ``` `+`被轉換為空格,通過過程`hex->char`,`%xy`這種形式的詞也被轉換為其ascii編碼是十六進制數`xy`的字符。 ```scheme (define hex->char (lambda (x y) (integer->char (string->number (string x y) 16)))) ``` 我們還需要一個處理POST方法傳輸數據的程序。輔助過程`parse-form-data-using-stdin`就是做這個的。 ```scheme (define parse-form-data-using-stdin (lambda () (let* ((content-length (getenv "CONTENT_LENGTH")) (content-length (if content-length (string->number content-length) 0)) (i 0)) (let par-loop ((par '())) (let ((c (read-char))) (set! i (+ i 1)) (if (or (> i content-length) (eof-object? c) (char=? c #\=)) (let arg-loop ((arg '())) (let ((c (read-char))) (set! i (+ i 1)) (if (or (> i content-length) (eof-object? c) (char=? c #\&)) (let ((par (url-decode (list->string (reverse! par)))) (arg (url-decode (list->string (reverse! arg))))) (table-put! *form-data-table* par (cons arg (table-get *form-data-table* par '()))) (unless (or (> i content-length) (eof-object? c)) (par-loop '()))) (arg-loop (cons c arg))))) (par-loop (cons c par)))))))) ``` POST方法通過腳本的標準輸入傳輸表單數據。傳輸的字符數放在環境變量`CONTENT_LENGTH`里。`parse-form-data-using-stdin`從標準輸入讀取對應的字符,也像之前那樣設置`*form-data-table*`,保證參數名和值被解碼。 剩下就是從`*form-data-table*`取回特定參數的值。主要這個這個表中每個參數都關聯著一個列表,這是為了適應一個參數多個值的情況。`form-data-get`取回一個參數對應的所有值。如果只有一個值,就返回這個值。 ```scheme (define form-data-get (lambda (k) (table-get *form-data-table* k '()))) ``` `form-data-get/1`返回一個參數的第一個(或最重要的)值。 ```scheme (define form-data-get/1 (lambda (k . default) (let ((vv (form-data-get k))) (cond ((pair? vv) (car vv)) ((pair? default) (car default)) (else ""))))) ``` 在我們目前的例子當中,CGI腳本都是生成純文本,通常我們希望生成一個HTML頁面。把CGI腳本和HTML表單結合起來生成一系列帶有表單的HTML頁面是很常見的。把不同方法的響應代碼放在一個CGI腳本里也是很常見的。不論任何情況,有一些輔助過程把字符串輸出為HTML格式(即,把HTML特殊字符進行編碼))都是很有幫助的: ```scheme (define display-html (lambda (s . o) (let ((o (if (null? o) (current-output-port) (car o)))) (let ((n (string-length s))) (let loop ((i 0)) (unless (>= i n) (let ((c (string-ref s i))) (display (case c ((#\<) "&lt;") ((#\>) "&gt;") ((#\") "&quot;") ((#\&) "&amp;") (else c)) o) (loop (+ i 1))))))))) ``` ## 17.4 一個CGI做的計算器 下面是一個CGI計算器的腳本,`cgicalc.scm`,使用了Scheme任意精度的算術功能。 ```scheme #!/bin/sh ":";exec /usr/local/bin/mzscheme -r $0 ;Load the CGI utilities (load-relative "cgi.scm") (define uhoh #f) (define calc-eval (lambda (e) (if (pair? e) (apply (ensure-operator (car e)) (map calc-eval (cdr e))) (ensure-number e)))) (define ensure-operator (lambda (e) (case e ((+) +) ((-) -) ((*) *) ((/) /) ((**) expt) (else (uhoh "unpermitted operator"))))) (define ensure-number (lambda (e) (if (number? e) e (uhoh "non-number")))) (define print-form (lambda () (display "<form action=\"") (display (getenv "SCRIPT_NAME")) (display "\"> Enter arithmetic expression:<br> <input type=textarea name=arithexp><p> <input type=submit value=\"Evaluate\"> <input type=reset value=\"Clear\"> </form>"))) (define print-page-begin (lambda () (display "content-type: text/html <html> <head> <title>A Scheme Calculator</title> </head> <body>"))) (define print-page-end (lambda () (display "</body> </html>"))) (parse-form-data) (print-page-begin) (let ((e (form-data-get "arithexp"))) (unless (null? e) (let ((e1 (car e))) (display-html e1) (display "<p> =&gt;&nbsp;&nbsp;") (display-html (call/cc (lambda (k) (set! uhoh (lambda (s) (k (string-append "Error: " s)))) (number->string (calc-eval (read (open-input-string (car e)))))))) (display "<p>")))) (print-form) (print-page-end) ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看