Template Engine
Sebelumnya kita sudah membuat webserver dinamis yang di dalamnya dapat menjalankan program lain contohnya seperti berikut code ini:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <! DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < meta http-equiv = "X-UA-Compatible" content = "IE=edge" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < title >Document</ title > </ head > < body > {% l = _QUERY_STRING['login'] u = _POST['username'] p = _POST['password'] emit(l) if (u == 'aku') and (p == 'saya'): emit('Login successful') else: emit('Login failed') %} </ body > </ html > |
Jadi, ketika webserver di jalankan maka baris kode yang ada didalam {%%} akan di eksekusi, sebelum di eksekusi baris kode tersebut perlu di ubah ke dalam baris kode yang dapat di eksekusi setelah itu dapat di render, untuk mengubah atau membaca program tersebut maka dibuatlah sebuah class yang diberi nama Template, kodenya seperti code berikut :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import re class Template(): """Compile an text into a template function""" def __init__( self , text): self .delimiter = re. compile (r '{%(.*?)%}' , re.DOTALL) self .tokens = self . compile (text) def kompile( self , text): tokens = [] for index, token in enumerate ( self .delimiter.split(text)): if index % 2 = = 0 : # plain string if token: tokens.append(( False , token.replace( '%\}' , '%}' ).replace( '{\%' , '{%' ))) else : # code block # find out the indentation lines = token.replace( '{\%' , '{%' ).replace( '%\}' , '%}' ).splitlines() # indent = 0 # for l in lines: # if l.strip(): # indent = min([len(l)- len(l.strip())]) # print(indent) indent = min ([ len (l) - len (l.lstrip()) for l in lines if l.strip()]) realigned = '\n' .join(l[indent:] for l in lines) tokens.append(( True , compile (realigned, '<tempalte> %s' % realigned[: 20 ], 'exec' ))) return tokens def render( self , context = None , * * kw): """Render the template according to the given context""" global_context = {} if context: global_context.update(context) if kw: global_context.update(kw) # add function for output def emit( * args): result.extend([ str (arg) for arg in args]) def fmt_emit(fmt, * args): result.append(fmt % args) global_context[ 'emit' ] = emit global_context[ 'fmt_emit' ] = fmt_emit # run the code result = [] for is_code, token in self .tokens: if is_code: exec (token, global_context) else : result.append(token) return ''.join(result) |
Lalu kita akan membedah kodenya mulai dari awal, di posisi teratas terdapat kode import re, re merupakan kependekan dari Regular Expression, Regular expression (regex) adalah deretan karakter yang digunakan untuk pencarian string atau teks dengan menggunakan pola (pattern). Contohnya penulisan email ryanhadi123890@gmail.com, jika diperhatikan dengan teliti email memiliki semacam pola, setelah huruf dan angka akan di ikuti dengan tanda @ lalu di ikuti huruf lalu titik dan huruf lagi. jadi Regex ini memudahkan kita mencari string tertentu dalam text yang banyak.
Setelah kita mengimport modul yang akan digunakan selanjutnya mulai menulis kode yang di awali dengan membuat class lalu diberi nama Template, kemudian kita membuat constructor, constructor adalah method yang pertama kali dijalankan ketika class atau objek dibuat, pada python untuk membuat constructor cara menuliskannya seperti code berikut :
1 | def __init__( self , text): |
Lalu untuk membuat function atau method di python di awali dengen def kemudian dilanjutkan dengan nama methodnya lalu parameter yang ada didalam kurung, karena method diatas merupakan constructor, maka nama methodnya harus _init_, di setiap class harus ada self untuk menjadi parameter pertamanya, dan juga ada parameter text yang dibuat untuk menampung data yang diterima dari luar class.
1 2 3 | def __init__( self , text): self .delimiter = re. compile (r '{%(.*?)%}' , re.DOTALL) self .tokens = self .kompile(text) |
Kemudian didalam constructor kita membuat properti self.delimiter yang di isi dengan hasil compile dari Regular Expression dan juga membuat properti self.token yang di isi dengan hasil method kompile dengan input text, lalu kita lanjutkan dengan method kompile.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def kompile( self , text): tokens = [] for index, token in enumerate ( self .delimiter.split(text)): if index % 2 = = 0 : # plain string if token: tokens.append(( False , token.replace( '%\}' , '%}' ).replace( '{\%' , '{%' ))) else : # code block # find out the indentation lines = token.replace( '{\%' , '{%' ).replace( '%\}' , '%}' ).splitlines() # indent = 0 # for l in lines: # if l.strip(): # indent = min([len(l)- len(l.strip())]) # print(indent) indent = min ([ len (l) - len (l.lstrip()) for l in lines if l.strip()]) realigned = '\n' .join(l[indent:] for l in lines) tokens.append(( True , compile (realigned, '<tempalte> %s' % realigned[: 20 ], 'exec' ))) return tokens |
Disini method kompile menerima parameter self dan text, kemudian kita membuat array kosong lalu di beri nama
1 | tokens = [] |
Kemudian lakukan perulangan seperti code berikut :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | for index, token in enumerate ( self .delimiter.split(text)): if index % 2 = = 0 : # plain string if token: tokens.append(( False , token.replace( '%\}' , '%}' ).replace( '{\%' , '{%' ))) else : # code block # find out the indentation lines = token.replace( '{\%' , '{%' ).replace( '%\}' , '%}' ).splitlines() indent = min ([ len (l) - len (l.lstrip()) for l in lines if l.strip()]) realigned = '\n' .join(l[indent:] for l in lines) tokens.append(( True , compile (realigned, '<tempalte> %s' % realigned[: 20 ], 'exec' ))) return tokens |
Code perulangan yang ada diatas digunakan untuk mendapatkan index dan token, index merupakan index dari text yang sudah di compile menggunakan Regular Expression, sehingga text berubah menjadi array dan memiliki index yang ambil oleh index dan token mengambil isi dari array, kemudian di lakukan pengecekan jika index merupakan modulus 2 dan juga ada token di dalamnya atau tidak kosong maka token akan dikirim apa adanya, dan ketika index bukan modulus 2 atau ganjil maka token akan di splitlines() kemudian di hilangkan tab atau indentasi karena pada python indentasi atau spasi sangat berpengaruh sekali, setelah disesuaikan tabnya maka diakukan realign atau penyusunan ulang setelah itu di kirim.
Setelah kode sudah di lakukan compile maka selanjutnya akan dilakukan eksekusi, method yang menjalankan eksekusi diberi nama render() yang memiliki parameter self dan context yang di inisialisasikan dengan None, kodenya seperti berikut :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | def render(self, context = None, **kw): """Render the template according to the given context""" global_context = {} if context: global_context.update(context) if kw: global_context.update(kw) # add function for output def emit(*args): result.extend([str(arg) for arg in args]) def fmt_emit(fmt, *args): result.append(fmt % args) global_context['emit'] = emit global_context['fmt_emit'] = fmt_emit # run the code result = [] for is_code, token in self.tokens: if is_code: exec(token, global_context) else: result.append(token) return ''.join(result) |
Kemudian buat lib baru yang bernama global_context, lalu dilakukan pengecekan ketika context memiliki isi, maka global_context akan diupdate dengan context lalu membuat function untuk mengeksekusi program
1 2 3 4 | def emit( * args): result.extend([ str (arg) for arg in args]) def fmt_emit(fmt, * args): result.append(fmt % args) |
Kemudian didalam global_context di masukan code berikut :
1 2 | global_context[ 'emit' ] = emit global_context[ 'fmt_emit' ] = fmt_emit |
Lalu dilakukan pengecekan jika self.token merupakan kode maka akan di eksekusi
1 2 3 4 5 6 | result = [] for is_code, token in self .tokens: if is_code: exec (token, global_context) else : result.append(token) |
Handle Post
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | def handle_post(url, http_version, data): url = "htdocs/%s" % (url) if os.path.exists(url) and not os.path.isdir(url): response_line = b' '.join([http_version.encode(), b' 200 ', b' OK']) content_type = mimetypes.guess_type(url)[ 0 ] or 'text/html' entity_header = b' '.join([b' Content - type : ', content_type.encode()]) file = open (url, 'r' ) html = file .read() file .close() template = Template(html) _POST = {} for x in data.split( '&' ): y = x.split( '=' ) _POST[y[ 0 ]] = y[ 1 ] print (_POST) context = { '_POST' : _POST } message_body = template.render(context).encode() else : response_line = b' '.join([http_version.encode(), b' 404 ', b' Not Found']) entity_header = b 'Content-Type: text/html' message_body = b '<h1>404 Not Found</h1>' crlf = b '\r\n' response = b''.join([response_line, crlf, entity_header, crlf, crlf, message_body]) return response |
Dikarenakan ada handle post maka terjadi perubahan pada handle request
1 2 3 4 5 6 | if(method == 'GET'): response = handle_get(url, http_version) elif(method == 'POST'): data = request_message[len(request_message)-1] response = handle_post(url, http_version,data) return response |
Dikarenakan handle post mengirim data, maka data tersebut dapat diambil pada request_line yang terakhir lalu dikirim sebagai parameter pada handle post untuk diproses, berikut kode penerapan method post
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <! DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < meta http-equiv = "X-UA-Compatible" content = "IE=edge" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < title >Document</ title > </ head > < body > < form action = "ceklogin.html" method = "POST" > < input type = "text" name = "username" > < input type = "password" name = "password" > < input type = "submit" value = "Login" > </ form > </ body > </ html > |
Kode diatas merupakan form login dan ketika tombol submit ditekan maka akan mengirim request ke server dengan method POST, lalu halaman akan di alihkan ke halaman ceklogin.html, proses yang terjadi pada handle_post hampir sama dengan handle_get yang membedakan adalah adanya data yang dikirim, ketika menekan tombol submit yang terjadi adalah browser mengirim method POST dengan url ceklogin.html, data username dan password yang kemudian data ini dimasukan kedalam context dengan kode perulangan di bawah ini :
1 2 3 | for x in data.split( '&' ): y = x.split( '=' ) _POST[y[ 0 ]] = y[ 1 ] |
lalu data yang sudah dimasukan tadi dirender dan diubah ke bentuk byte yang selanjutnya disimpan ke massage_body untuk di return.
Komentar
Posting Komentar