位元詩人 [Web] 程式設計教學:以 CGI 程式回應 Ajax 請求

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在 CGI 盛行時,網頁程式多以表單傳送資料;而 Ajax 普及後,已很少再使用 CGI。不過 CGI 並未限制 HTTP 方法,因此仍可回應 Ajax 請求。

本文示範以 CGI 程式回應 Ajax 呼叫的方式。

在 CGI 程式中偵測 HTTP 方法 (HTTP Method)

先確認 CGI 程式可取得 HTTP 方法。

program main;
uses
  Dos;

const
  newline = ANSIChar(#10);
var
  method : string;
begin
  method := GetENV('REQUEST_METHOD');

  Write('Content-Type: text/plain; charset=utf-8' + newline + newline);

  if '' = method then
    Write('Unknown HTTP method')
  else
    Write('HTTP method: ' + method);
end.

編譯:

$ fpc -oindex.cgi detectHTTPMethod.pas

以瀏覽器存取:

HTTP method: GET

若要測試其他方法,可使用 HTTP client,例如:

$ http PUT http://localhost:8080/cgi-bin/index.cgi

不同 CGI / FastCGI 實作支援的 HTTP 方法可能不同,可自行測試。

以 CGI 程式搭配 Ajax 呼叫

Ajax 本質是發送 HTTP request 並接收回應。 CGI 只負責讀取 request 並輸出 response,因此同樣可以配合 Ajax 使用。

以下為簡化後的前端程式:

function showMessage(message) {                            /*  1 */
    let msg = document.getElementById('message');          /*  2 */
    msg.innerHTML =                                        /*  3 */
        '<div class="alert alert-warning" role="alert">'   /*  4 */
        + message                                          /*  5 */
        + '</div>';                                        /*  6 */
}                                                          /*  7 */

function clearMessage() {                                  /*  8 */
    let msg = document.getElementById('message');          /*  9 */
    msg.innerHTML = '';                                    /* 10 */
}                                                          /* 11 */

function showInfo(data) {                                  /* 12 */
    let info = document.getElementById('info');            /* 13 */
    info.innerHTML =                                       /* 14 */
        '<div class="alert alert-info" role="alert">'      /* 15 */
        + data                                             /* 16 */
        + '</div>';                                        /* 17 */
}                                                          /* 18 */

function clearInfo() {                                     /* 19 */
    let info = document.getElementById('info');            /* 20 */
    info.innerHTML = '';                                   /* 21 */
}                                                          /* 22 */

let btn = document.getElementById('btnInch2CM');           /* 23 */
btn.addEventListener('click', function () {                /* 24 */
    let value = document.getElementById('inch2cm').value;  /* 26 */

    if ('' === value) {                                    /* 27 */
        clearInfo()                                        /* 28 */
        showMessage('No data');                            /* 29 */
    } else {                                               /* 30 */
        clearMessage();                                    /* 31 */
        document.getElementById('inch2cm').value = '';     /* 33 */

        superagent                                         /* 34 */
            .put('/cgi-bin/inch2cm.cgi')                   /* 35 */
            .send({ data: value })                         /* 36 */
            .then(function (res) {                         /* 37 */
                showInfo(res.body.data);                   /* 38 */
            })                                             /* 39 */
            .catch(function (err) {                        /* 40 */
                if (err.response) {                        /* 41 */
                    showMessage(err.response.message);     /* 42 */
                } else {                                   /* 43 */
                    showMessage(err);                      /* 44 */
                }                                          /* 45 */
            });                                            /* 46 */
    }                                                      /* 47 */
});                                                        /* 48 */

此範例以 PUT 方法送出 JSON:

{
    "data": 100
}

CGI 程式

以下 CGI 程式負責接收資料、驗證並回傳 JSON:

program main;                                                     (*   1 *)
{$mode objfpc}                                                    (*   2 *)
uses                                                              (*   3 *)
  dos, fpjson, jsonparser, sysUtils, termio;                      (*   4 *)

function inchToCM(input: real): real;                             (*   5 *)
begin                                                             (*   6 *)
  inchToCM := input * 2.54;                                       (*   7 *)
end;                                                              (*   8 *)

const                                                             (*   9 *)
  newline = ANSIChar(#10);                                        (*  10 *)

var                                                               (*  11 *)
  method : string;                                                (*  12 *)
  data : TJSONData;                                               (*  13 *)
  json : TJSONObject;                                             (*  14 *)
  c : char;                                                       (*  15 *)
  s : ansistring;                                                 (*  16 *)
  value : real;                                                   (*  17 *)
  out : real;                                                     (*  18 *)

begin                                                             (*  19 *)
  method := GetEnv('REQUEST_METHOD');                             (*  20 *)

  if 'PUT' <> method then                                         (*  22 *)
  begin                                                           (*  23 *)
    Write('HTTP/1.1 405 Method Not Allowed' + newline);           (*  24 *)
    Write('Content-Type: application/json' + newline + newline);  (*  25 *)

    json := TJSONObject.Create();                                 (*  26 *)
    json.Add('message', 'Method Not Allowed');                    (*  27 *)
    Write(json.AsJSON);                                           (*  28 *)
    Halt(0);                                                      (*  29 *)
  end;                                                            (*  30 *)

  s := '';                                                        (*  31 *)
  while not eof(input) do                                         (*  32 *)
  begin                                                           (*  33 *)
    read(c);                                                      (*  34 *)
    s := s + c;                                                   (*  35 *)
  end;                                                            (*  36 *)

  try                                                             (*  38 *)
    data := GetJSON(s);                                           (*  39 *)
    json := TJSONObject(data);                                    (*  40 *)
  except                                                          (*  41 *)
    Write('HTTP/1.1 422 Unprocessable Entity' + newline);         (*  42 *)
    Write('Content-Type: application/json' + newline + newline);  (*  43 *)

    json := TJSONObject.Create();                                 (*  44 *)
    json.Add('message', 'Wrong data format');                     (*  45 *)
    Write(json.AsJSON);                                           (*  46 *)
    Halt(0);                                                      (*  47 *)
  end;                                                            (*  48 *)

  try                                                             (*  50 *)
    s := json.Get('data');                                        (*  51 *)
  except                                                          (*  52 *)
    Write('HTTP/1.1 422 Unprocessable Entity' + newline);         (*  53 *)
    Write('Content-Type: application/json' + newline + newline);  (*  54 *)

    json := TJSONObject.Create();                                 (*  55 *)
    json.Add('message', 'No data');                               (*  56 *)
    Write(json.AsJSON);                                           (*  57 *)
    Halt(0);                                                      (*  58 *)
  end;                                                            (*  59 *)

  if '' = s then                                                  (*  61 *)
  begin                                                           (*  62 *)
    Write('HTTP/1.1 422 Unprocessable Entity' + newline);         (*  63 *)
    Write('Content-Type: application/json' + newline + newline);  (*  64 *)

    json := TJSONObject.Create();                                 (*  65 *)
    json.Add('message', 'Empty data');                            (*  66 *)
    Write(json.AsJSON);                                           (*  67 *)
    Halt(0);                                                      (*  68 *)
  end;                                                            (*  69 *)

  try                                                             (*  71 *)
    value := StrToFloat(s);                                       (*  72 *)
  except                                                          (*  73 *)
    Write('HTTP/1.1 422 Unprocessable Entity' + newline);         (*  74 *)
    Write('Content-Type: application/json' + newline + newline);  (*  75 *)

    json := TJSONObject.Create();                                 (*  76 *)
    json.Add('message', 'Wrong data');                            (*  77 *)
    Write(json.AsJSON);                                           (*  78 *)
    Halt(0);                                                      (*  79 *)
  end;                                                            (*  80 *)

  out := inchToCM(value);                                         (*  81 *)

  Write('Content-Type: application/json' + newline + newline);    (*  83 *)

  json := TJSONObject.Create();                                   (*  84 *)
  json.Add('data', format('%.3f', [out]));                        (*  85 *)
  Write(json.AsJSON);                                             (*  86 *)
end.                                                              (*  87 *)

結語

CGI 並未限制 HTTP 方法,也不限制回應格式。只要符合 HTTP request / response 的規則,即可回應 Ajax 呼叫。

另見

關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。