為開發人員,我們依賴于靜態分析工具來檢查、lint(分析)和轉換我們的代碼。我們使用這些工具來幫助我們提高生產效率并生成更好的代碼。然而,當我們使用markdown編寫內容時,可用的工具就很少。
在本文中,我們將介紹如何開發一個Markdown擴展來解決在使用Markdown管理Django站點中的內容時遇到的挑戰。
你認為他們有linter嗎?
照片來自Pexels,由mali maeder拍攝
像每個網站一樣,我們在主頁、FAQ部分和“關于”頁面等地方都有不同類型的(大部分)靜態內容。很長一段時間以來,我們都是在Django模板中直接管理這些內容的。
當我們最終決定是時候將這些內容從模板轉移到數據庫中時,我們認為最好使用Markdown。從Markdown生成HTML更安全,它提供了一定程度的控制和一致性,并且對于非技術用戶來說更容易處理。隨著我們轉移過程的進展,我們注意到我們遺漏了一些東西:
當URL更改時,鏈接到內部頁面的鏈接可能會中斷。在Django模板和視圖中,我們使用了reverseand {% url %},但是這在普通的Markdown中是不可用的。
絕對內部連接不能在不同環境之間進行復制。這可以使用相對鏈接來解決,不過目前沒有開箱即用的增強這一點的方法。
無效鏈接會損害用戶體驗,并導致用戶質疑整個內容的可靠性。這并不是Markdown獨有的東西,只不過HTML模板是由對URL有一定了解的開發人員維護的。另一方面,Markdown文檔是為非技術寫作人員設計的。
當我研究這個問題時,我搜索了Python linters、Markdown預處理器和擴展來幫助生成更好的Markdown。結果都不是很好。一個引人注目的方法是使用Django模板來生成Markdown文檔。
使用Django模板,你可以使用諸如url之類的模板標記來反向查詢URL名稱,并配合使用條件、變量、日期格式和所有其他Django模板特性。這種方法本質上是使用Django模板作為Markdown文檔的預處理程序。
我個人認為這可能不是非技術作家的最佳解決方案。另外,我擔心提供對Django模板標記的訪問可能是危險的。
對這個問題有了更好的理解之后,我們準備在Python中更深入地研究Markdown。
要在Python中開始使用Markdown,我們先安裝markdown包:
接著,創建一個Markdown對象并使用其函數將一些Markdown轉換成HTML:
你現在可以在你的模板中使用這個HTML代碼片段。
基本的Markdown處理器提供了生成HTML內容的基本要素。對于更“新奇”的選項,Python markdown包包含了一些內置擴展。一個流行的擴展是“extra”擴展,除了其他東西之外,它增加了對隔離代碼塊的支持:
為了使用我們獨特的Django功能擴展Markdown,我們將開發自己的擴展。
如果你查看源代碼,你將看到要將markdown轉換為HTML, Markdown會使用多種不同的處理器。一種類型的處理器是內聯處理器。內聯處理器會匹配特定的內聯模式,如鏈接、反引號、粗體文本和帶下劃線的文本,并將它們轉換為HTML。
我們的Markdown擴展的主要目的是驗證和轉換鏈接。因此,我們最感興趣的內聯處理器是LinkInlineProcessor。這個處理器以[Haki的網站](https://hakibenito.com)的形式獲取markdown ,解析它并返回一個包含鏈接和文本的元組。
為了擴展該功能,我們擴展了LinkInlineProcessor并創建了一個Markdown.Extension, 我們用它來處理鏈接:
我們來將這段代碼分解一下::
DjangoUrlExtension擴展注冊了一個名為DjangoLinkInlineProcessor的內聯鏈接處理器。這個處理器將取代任何其他現有的鏈接處理器。
內聯處理器DjangoLinkInlineProcessor擴展了內置的LinkInlineProcessor,并在它處理的每個鏈接上調用clean_link函數。
clean_link函數接收一個鏈接和一個域名,并返回一個轉換后的鏈接。這就是我們要插入我們的實現的地方。
如何獲得網站域名
要識別到你自己網站的鏈接,你必須知道你的網站的域名。如果你正在使用Django的sites框架,那么你可以使用它來獲取當前域名。
我沒有把它包含在我的實現中,因為我們沒有使用sites框架。相反,我們在Django設置中設置了一個變量。
獲取當前域名的另一種方法是使用HttpRequest對象。如果內容只在你自己的站點中被編輯,你可以嘗試從請求對象中插入站點域名。這可能需要對你的實現進行一些更改。
要使用該擴展,請在初始化一個新的Markdown實例時添加它:
太好了,這個擴展已經被使用了,我們準備進入有趣的部分了!
既然我們得到了在所有鏈接上調用clean_link的擴展,那我們可以來實現我們的驗證和轉換邏輯。
要開始工作,我們將從一個簡單的驗證開始。mailto鏈接對于使用預定義的收件人地址、主題甚至消息正文打開用戶的電子郵件客戶端非常有用。
一個常見的mailto鏈接是這樣的:
這個鏈接將打開你的電子郵件客戶端,并設置成撰寫一封主題行為“我需要幫助!”的新電子郵件給“support@service.com”。
mailto鏈接不一定非要包含電子郵件地址。如果你看一看這篇文章底部的“分享”按鈕,你會發現像這樣的一個mailto鏈接:
這個mailto鏈接沒有包含收件人,僅包含了主題行和消息正文。
既然我們已經很好地理解了mailto鏈接是什么樣子的,我們就可以向clean_link函數添加第一個驗證:
為了驗證mailto鏈接,我們向clean_link中添加了以下代碼:
檢查鏈接是否以mailto:開頭,以識別相關鏈接。
使用正則表達式將鏈接分割到它的組件。
從mailto鏈接中刪除實際的電子郵件地址,并使用Django的EmailValidator驗證它。
注意,我們還添加了一種名為InvalidMarkdown的新異常類型。我們定義了自己的自定義異常類型,以將它與markdown本身所引發的其他錯誤區分開來。
自定義錯誤類
我曾經寫過關于自定義錯誤類的文章,為什么它們是有用的,以及你什么時候應該使用它們。
在我們繼續之前,讓我們添加一些測試,看看它的實際效果:
太棒了!按預期的運行了。
既然我們已經了解了mailto鏈接,我們也可以處理其他類型的鏈接:
外部鏈接
我們的Django應用程序外部的鏈接。
必須包含一個頁面跳轉協議(scheme):http或https。
理想情況下,我們還希望確保這些鏈接沒有被破壞,但我們現在不會這樣做。
內部鏈接
到我們的Django應用程序中的頁面的鏈接。
鏈接必須是相對的:這將允許我們在不同環境之間移動內容。
使用Django的URL名稱而不是一個URL路徑:這將允許我們安全地來回移動視圖,而不必擔心markdown內容中的失效鏈接。
鏈接可能包含查詢參數(?)和片段(#)。
SEO
從SEO的角度來看,公共URL不應該改變。當他們這樣做的時候,你應該使用重定向正確地處理它,否則你可能會受到搜索引擎的懲罰。
有了這個需求列表,我們就可以開始工作了。
解析URL名稱
要鏈接到內部頁面,我們希望編寫者提供一個URL名稱,而不是URL路徑。例如,假設我們有這個視圖:
這個頁面的URL路徑是https://example.com/, URL名稱是home。我們想要在我們的markdown鏈接中使用這個URL名稱home,就像這樣:
這將渲染到:
我們還想支持查詢參數和散列:
這將渲染到以下HTML:
在使用URL名稱時,如果我們更改了URL路徑,內容中的鏈接將不會被破壞。要檢查作者提供的href是否是一個有效的url_name,我們可以嘗試reverse它:
URL名稱“home”指向URL路徑“/”。當沒有匹配項時,將會引發一個異常:
在我們繼續之前,當URL名稱包含查詢參數或散列時,會發生什么:
這是有意義的,因為查詢參數和散列不是URL名稱的一部分。
要使用reverse并支持查詢參數和散列,我們首先需要清除值。然后,檢查它是一個有效的URL名稱,并返回包含查詢參數和散列的URL路徑,如果提供了的話:
這個代碼段使用一個正則表達式來以?或#的出現對href進行分割,并返回各部分。
請確保它可以工作:
太了不起了!作者們現在可以在Markdown中使用URL名稱了。它們還可以包括要添加到該URL的查詢參數和片段。
處理外部鏈接
要正確處理外部鏈接,我們需要檢查兩件事:
1.外部鏈接總是提供一個跳轉協議,http:或者https:。
2.阻止到我們自己網站的絕對鏈接。內部鏈接應該使用URL名稱。
到目前為止,我們已經處理了URL名稱和mailto鏈接。如果我們通過了這兩個檢查,這意味著href是一個URL。讓我們從檢查鏈接是否是鏈接到我們自己的網站開始:
函數urlparse會返回一個命名元組,該元組包含URL的不同部分。如果netloc屬性等于site_domain,那么該鏈接就確實是一個內部鏈接。
如果URL實際上是內部的,我們就需要終止。但是,請記住,作者們不一定是技術人員,因此我們希望幫助他們,并提供一個有用的錯誤消息。我們要求該內部鏈接使用URL名稱而不是URL路徑,所以最好讓作者們知道他們提供的路徑的URL名稱。
要獲得一個URL路徑的URL名稱,Django為我們提供了一個名為resolve的函數:
當找到匹配項時,resolve會返回一個ResolverMatch對象,其中包含URL名稱和其他信息。當沒有找到匹配項時,它就會引發一個錯誤:
這實際上就是Django在底層所做的工作,用來確定在一個新請求到來時執行哪個視圖函數。
為了給作者們提供更好的錯誤信息,我們可以使用來自ResolverMatch對象的URL名稱:
當我們識別出內部鏈接時,我們要處理兩種情況:
我們沒有識別出這個URL:這個URL很可能是不正確的。請作者檢查該URL是否有錯誤。
我們識別出了這個URL: 這個URL是正確的,所以就告訴作者應該使用什么URL名稱。
我們來實際地看一下它:
漂亮!外部鏈接被接受,內部鏈接被拒絕,并帶有一個有用的消息。
要求跳轉協議
我們要做的最后一件事是確保外部鏈接包含一個跳轉協議,要么是http:,要么是https:。讓我們將這最后一部分添加到函數clean_link:
使用解析后的URL,我們可以很容易地檢查跳轉協議。讓我們確保它正在工作:
我們向這個函數提供了一個沒有跳轉協議的鏈接,但是它運行失敗了,并顯示了一條有用的消息。太酷了!
這是clean_link函數的全部代碼:
要了解所有這些特性的一個實際用例是什么樣子的,請看下面的內容:
這將產生以下HTML:
不錯!
我們現在有一個很不錯的擴展,它可以驗證和轉換Markdown文檔中的鏈接!現在,在不同環境之間移動文檔和保持內容整潔要容易多了,最重要的是,可以保持正確和最新!
源碼
你可以在這個gist中找到全部源代碼。(地址:https://gist.github.com/hakib/73fccc340e855bb65f42197e298c0c7d )
本文中所描述的功能對我們很有用,但是你可能需要根據自己的需求對它進行調整。
如果你需要一些想法,那么除了這個擴展之外,我們還創建了一個markdown Preprocessor,它允許作者們在markdown中使用常量。例如,我們定義了一個名為SUPPORT_EMAIL的常量,我們像這樣使用它:
該預處理程序將用我們定義的文本替換字符串$SUPPORT_EMAIL,然后才渲染Markdown。
英文原文:https://hakibenita.com/django-markdown
譯者:Nothing
、選擇題(1-18題各3分19-36題各2分,共92分)
1.在HTML的<TD>標簽中,align 屬性的取值是( C )
A. top ; B. middle ; C. center ; D. bottom
<table border="1">
<tr>
<td width="100px">姓名</td>
<td>性別</td>
<td>年齡</td>
</tr>
<tr>
<td>張三</td>
<td>男</td>
<td>20齡</td>
</tr>
</table>
2. CSS樣式表根據所在網頁的位置,可分為( B )
A.行內樣式表、內嵌樣式表、混合樣式表; B.行內樣式表、內嵌樣式表、外部樣式表;
C.外部樣式表、內嵌樣式表、導入樣式表; D.外部樣式表、混合樣式表、導入樣式表
行內樣式:
<html>
<body>
<div style="width:100px;height:100px;background:red;"></div>>
</body>
</html>
-----------------------------------------------------------------
內嵌樣式:
<html>
<head>
<style type="text/css">
#div{width:100px;height:100px;background:red;}
</style>
</head>
<body>
<div id="div"></div>
</body>
</html>
--------------------------------------------------------------
外部樣式:
<html>
<head>
<link rel="stylesheet" type="text/css" href="ccss.css">
</head>
<body>
<div id="div"></div>>
</body>
</html>
---------------------
css文件
#div{width:100px;height:100px;background:red;}
#和.區別
.點是使用class引用的,多個控件可以同時使用一個class,一個控件上也可以使用多個class,比如
.tdRed{border:solid 1px red;}
.tdBKBlue{background-color:blue;}
<td class="tdRed" />
<td class="tdRed tdBKBule"/>
而ID是在一個頁面中唯一的
總得來說class表示泛性的,id表示個性的
比如你所有的按鈕都是一個顏色的
.normalButton{background-color:blue;border:solid 0px black;}
對于提交按鈕會要做的大一點
#submit{width:100px;height:100px;}
那么你的按鈕就是
<input type="button" id="submit" class="normalButton" value="提交" />
普通的按鈕就是
<input type="button" id="abcdefg" class="normalButton" value="普通按鈕" />
3. 在插入圖片標簽中,對插入的圖片進行文字說明使用的屬性是( D )
A.name; B.id; C.src; D. alt
4. 對于<FORM action=″URL″ method=*>標簽,其中*代表GET或( C )
A.SET; B. PUT; C. POST ; D. INPUT
Get和post區別
安全性:POST比GET安全;
編碼方式:POST方式提交時可以通過HTML文檔中的<META>元素設置實體部分的編碼方式,而GET方式提交時URI默認的編碼方式為ISO-8859-1,不可以在頁面中設置;
傳輸文件大小:POST方式提交文件放在實體部分傳輸,大小無上限,而GET方式提交文件內容放在URI部分傳輸,最大為2KB;
請求速度:GET比POST快。
數據傳輸方式:GET:查詢字符串(名稱/值對)是在 GET 請求的 URL 中發送的,如:/test/demo_form.asp?name1=value1&name2=value2;POST:查詢字符串(名稱/值對)是在 POST 請求的 HTTP 消息主體中發送的。
5. 下列標簽可以不成對出現的是( B )
A.〈HTML〉〈/HTML〉 ; B.〈P〉 〈/P〉; C.〈TITLE〉〈/TITLE〉 ; D.〈BODY〉〈/BODY〉
<p>是段落標簽。
在HTML4.01中某些標簽(<p><br>,<hr>,<img>, <input>,<link>等)允許不成對出現,但是不推薦。在現在的瀏覽器里,都會“兼容”這些單標簽。瀏覽器解釋<p>標簽后,碰到一個不對應的標簽時,會自動填補</p>。所以<p>標簽可以單標簽使用,但不推薦。
在HTML5中規定了元素必須始終關閉,也就是標簽必須成對出現。
6. 對于標簽〈input type=*〉,如果希望實現密碼框效果,*值是( C )
A. hidden; B.text ; C. password ; D. submit
7. HTML代碼<select name=“name”></select>表示?( D )
A. 創建表格 ; <table>
B. 創建一個滾動菜單; <marquee>
C. 設置每個表單項的內容;
D.創建一個下拉菜單
8. BODY元素用于背景顏色的屬性是( C )
A. alink ; B. vlink ; C. bgcolor; D. background
9. 在表單中包含性別選項,且默認狀態為“男”被選中,下列正確的是( A )
A. <input type=radio name=sex checked> 男 ; B.<input type=radio name=sex enabled>
C.<input type=checkbox name=sex checked>男;
D.nput type=checkbox name=sex enabled>男
性別(單選框):<input type="radio" value="1" name="sex" checked="checked"/>男
<input type="radio" value="2" name="sex"/>女
角色(下拉框):<select name="role">
<option value="1" selected="selected">教師</option>
<option value="2">學生</option>
</select>
10. 在CSS中下面哪種方法表示超鏈接文字在鼠標經過時,超鏈接文字無下劃線?( B )
A. A:link{TEXT-DECORATION: underline }; B. A:hover {TEXT-DECORATION: none};
C. A:active {TEXT-DECORATION: blink }; D. A:visited {TEXT-DECORATION: overline }
11. JavaScript代碼: 'abcdefg'.indexOf('D') 結果是( B )
A:0 B:-1 C:3 D:4
Js常用方法
1.substr
substr(start,length)表示從start位置開始,截取length長度的字符串。
var src="images/off_1.png";
alert(src.substr(7,3));
彈出值為:off
2.substring
substring(start,end)表示從start到end之間的字符串,包括start位置的字符但是不包括end位置的字符。
var src="images/off_1.png";
alert(src.substring(7,10));
彈出值為:off
3.indexOF
indexOf() 方法返回某個指定的字符串值在字符串中首次出現的位置(從左向右)。沒有匹配的則返回-1,否則返回首次出現位置的字符串的下標值。
var src="images/off_1.png";
alert(src.indexOf('t'));
alert(src.indexOf('i'));
alert(src.indexOf('g'));
彈出值依次為:-1,0,3
4.lastIndexOf
lastIndexOf()方法返回從右向左出現某個字符或字符串的首個字符索引值(與indexOf相反)
var src="images/off_1.png";
alert(src.lastIndexOf('/'));
alert(src.lastIndexOf('g'));
彈出值依次為:6,15
5.split
將一個字符串分割為子字符串,然后將結果作為字符串數組返回。
以空格分割返回一個子字符串返回
var s, ss;
var s = "1,2,3,4";
ss = s.split(",");
alert(ss[0]);
alert(ss[1]);
12. <img src="name">的意思是?( A )
A. 圖像相對于周圍的文本左對齊; B. 圖像相對于周圍的文本右對齊;
C. 圖像相對于周圍的文本底部對齊; D. 圖像相對于周圍的文本頂部對齊
13. 點擊按鈕,在ID為“Link”的DIV標簽內顯示東軟實訓超鏈接, 下面對該按鈕的onClick事件函數描述正確的是:C
A. Link.innerText='<a ;
B. Link.outerText='<a ;
C. Link.innerHTML='<a ;
D. Link.outerHTML='<a
innerHTML 設置或獲取位于對象起始和結束標簽內的
HTML
outerHTML 設置或獲取對象及其內容的 HTML 形式
innerText 設置或獲取位于對象起始和結束標簽內的文本
outerText 設置(包括標簽)或獲取(不包括標簽)對象的文本
innerText和outerText在獲取時是相同效果,但在設置時,innerText僅設置標簽內的文本,而outerText設置包括標簽在內的文本
14.(“24.7” + 2.3 ) 的計算結果是( C )
A. 27 ; B. 24.7 2.3; C. 24.72.3; D. 26.7
15. ( B )事件處理程序可用于在用戶單擊按鈕時執行函數
A. onSubmit; B. onClick; C. onChange; D. onExit
屬性當以下情況發生時,出現此事件onabort圖像加載被中斷onblur元素失去焦點onchange用戶改變域的內容onclick鼠標點擊某個對象ondblclick鼠標雙擊某個對象onerror當加載文檔或圖像時發生某個錯誤onfocus元素獲得焦點onkeydown某個鍵盤的鍵被按下onkeypress某個鍵盤的鍵被按下或按住onkeyup某個鍵盤的鍵被松開onload某個頁面或圖像被完成加載onmousedown某個鼠標按鍵被按下onmousemove鼠標被移動onmouseout鼠標從某元素移開onmouseover鼠標被移到某元素之上onmouseup某個鼠標按鍵被松開onreset重置按鈕被點擊onresize窗口或框架被調整尺寸onselect文本被選定onsubmit提交按鈕被點擊onunload用戶退出頁面
16. 用戶更改表單元素 Select 中的值時,就會調用( D )事件處理程序
A. onClick; B. onFocus; C. onMouseOver; D. onChange
17.onMouseUp 事件處理程序表示( A )
A. 鼠標被釋放; B. 鼠標按下; C. 鼠標離開某個區域; D. 鼠標單擊
18. 下列哪一項表示的不是按鈕( C )
A. type="submit"; B. type="reset"; C. type="image"; D. type="button"
<img src="/i/eg_tulip.jpg" alt="上海鮮花港 - 郁金香" />
19.下面哪一項是換行符標簽?( C )
A. <body>; B. <font>; C. <br>; D. <p>
font規定文本字體、大小和顏色:
<font size="3" color="red">This is some text!</font>
<font size="2" color="blue">This is some text!</font>
<font face="verdana" color="green">This is some text!</font>
20. 下列哪一項是在新窗口中打開網頁文檔。( B )
A. _self; B. _blank; C. _top; D. _parent
_blank在新窗口中打開被鏈接文檔;
_self是指在本身這個網頁窗口來打開新的網頁鏈接;
_top表示在頂層窗口打開網頁鏈接,即在整個窗口中打開被鏈接文檔;
_parent表示在父窗口打開網頁鏈接;
<a target="_blank">Visit W3School!</a>onclick="javascript:window.open('Default.aspx','_blank');"21. 下面說法錯誤的是( D )
A. CSS樣式表可以將格式和結構分離;
B. CSS樣式表可以控制頁面的布局;
C. CSS樣式表可以使許多網頁同時更新;
D. CSS樣式表不能制作體積更小下載更快的網頁
CSS樣式表能為我們實現些什么樣的功能?
1、你可以將格式和結構分離。
2、你可以以前所未有的能力控制頁面布局。
3、你可以制作體積更小下載更快的網頁。
4、你可以將許多網頁同時更新,比以前更快更容易。
5、瀏覽器將成為你更友好的界面
將格式和結構分離
HTML從來沒打算控制網頁的格式或外觀。這種語言定義了網頁的結構和各要素的功能,而讓瀏覽器自己決定應該讓各要素以何種模樣顯示。 但是網頁設計者要求的更多。所以當 Netscape推出新的可以控制網頁外觀的HTML標簽時,網頁設計者無不歡呼雀躍。 我們可以用<FONT FACE>、<I>包在<P>外邊控制文章主體的外觀等等。然后我們將所有東西都放入表格,用隱式GIF空格 產生一個20象素的邊距。一切都變得亂七八糟。編碼變得越來越臃腫不堪,要想將什么內容迅速加到網頁中變得越來越難。 串接樣式表通過將定義結構的部分和定義格式的部分分離使我們能夠對頁面的布局施加更多的控制。HTML仍可以保持簡單明了的初衷。CSS代碼獨立出來從另一角度控制頁面外觀。
以前所未有的能力控制頁面的布局
<FONT SIZE>能使我們調整字號,表格標簽幫助我們生成邊距,這都沒錯。但是,我們對HTML總體上的控制卻很有限。我們不可能精確地生成80象素的高度,不可能控制行間距或字間距,我們不能在屏幕上精確定位圖象的位置。但是現在,樣式表使這一切都成為可能。
可以制作出體積更小下載更快的網頁還有更好的消息:
樣式表只是簡單的文本,就象HTML那樣。它不需要圖象,不需要執行程序,不需要插件,不需要流式。它就象HTML指令那樣快。有了CSS之后,以前必須求助于GIF的事情現在通過CSS就可以實現。還有,正如我先前提到的,使用串接樣式表可以減 少表格標簽及其它加大HTML體積的代碼, 減少圖象用量從而減少文件尺寸。
可以更快更容易地維護及更新大量的網頁
沒有樣式表時,如果我想更新整個站點中所有主體文本的字體,我必須一頁一頁地修改每張網頁。即便站點用數據庫提供服務,我仍然需要更新所有的模板, 而且更新每一模板中每一個實例實例的 <FONT FACE>。樣式表的主旨就是將格式和結構分離。 利于樣式表,我可以將站點上所有的網 頁都指向單一的一個CSS文件,我只要 修改CSS文件中某一行,那么整個站點 都會隨之發生變動。
瀏覽器將成為你更友好的界面
不象其它的的網絡技術,樣式表的代碼 有很好的兼容性,也就是說,如果用戶 丟失了某個插件時不會發生中斷,或者 使用老版本的瀏覽器時代碼不會出現雜 亂無章的情況。 只要是可以識別串接樣式表的瀏覽器就 可以應用它。
22. 要使表格的邊框不顯示,應設置border的值是( B )
A. 1; B. 0; C. 2; D. 3
23. 如果要在表單里創建一個普通文本框,以下寫法中正確的是( A )
A. <INPUT>; B. <INPUT type="password">;
C. <INPUT type="checkbox">; D. <INPUT type="radio">
24. 以下有關按鈕的說法中,錯誤的是( B )
A. 可以用圖像作為提交按鈕; B. 可以用圖像作為重置按鈕;
C. 可以控制提交按鈕上的顯示文字; D. 可以控制重置按鈕上的顯示文字。
<input type="image" src="pic.jpg" onclick="fangfa();"/>
function fangfa(){
document.formname.submit();
document.formname.reset();
}
先來看一段代碼,啟動一個靜態的http服務
import CBHttp.code
function main(parm)
{
var httpServer = new HttpServer();
httpServer.startServer();
while(1) //主線程不能退出
{
Sleep(1000);
}
}
服務啟動好了,假設我們的項目根目錄是E:\cbrotherwork\httpserver,那么我們的服務根路徑就默認是E:\cbrotherwork\httpserver\webroot
我們新建txt文件E:\cbrotherwork\httpserver\webroot1.txt,在文本里輸入內容Hello HttpServer!。
然后我們打開本機瀏覽器輸入:http://127.0.0.1:8000/111.txt,你發現你在網頁上訪問到了111.txt
主流的文件格式都支持下載
靜態下載的過程中,有時需要控制可下載的文件格式,那么就要手動調配MIME
import CBHttp.code
function main(parm)
{
var httpServer = new HttpServer();
httpServer.addMIME("mystyle","text/html"); //添加后綴名為.mystyle,mime為text/html
httpServer.addMIME("c"); //添加后綴名為.c,mime類型讓系統自動識別
httpServer.removeMIME("gif"); //刪除gif
var mimiemap = httpServer.getMIME(); //獲取目前支持的mime類型
mimiemap.begin();
do
{
print mimiemap.getKey() + ":" + mimiemap.getValue();
}
while(mimiemap.next());
httpServer.startServer();
while(1)
{
Sleep(1000);
}
}
運行結果:
rar:application/octet-stream
bmp:image/bmp
xml:application/xml
html:text/html
c:text/plain
js:application/x-javascript
txt:text/plain
ico:image/x-icon
zip:application/zip
jpg:image/jpeg
mystyle:text/html
7z:application/octet-stream
json:application/json
swf:application/x-shockwave-flash
css:text/css
png:image/png
我們通常需要在用戶調用一個接口時通過一些條件來返回不同的內容,下面我們再通過一段代碼看一下
import CBHttp.code
class HelloAction
{
function DoAction(request,respon) //這個函數寫法是固定的,request表示客戶端請求信息,respon是回復給客戶機的信息
{
var myTime = new Time();
respon.write("now time is:" + myTime.strftime("%Y/%m/%d %H:%M:%S"));
respon.flush();
}
}
function main(parm)
{
var httpServer = new HttpServer();
httpServer.addAction("hello.cb",new HelloAction()); //我們注冊接口hello.cb,響應類是HelloAction
httpServer.startServer();
while(1) //主線程不能退出
{
Sleep(1000);
}
}
然后我們瀏覽器輸入:http://127.0.0.1:8000/hello.cb,每次訪問返回的都是當前時間。
接口名字可以是任意字符串,官方建議用統一的后綴,方便跟其他web服務器協作。
函數 | 描述 | 用法 |
addAction(name,actobj) | 添加http響應接口,name為接口名字,actobj為響應的Action對象 | httpServer.addAction("hello.cb",actobj) |
startServer(port,listen_ip) | 啟動服務 | httpServer.startServer(port,listen_ip) |
stopServer() | 停止服務 | httpServer.stopServer() |
setRoot(path) | 設置服務器跟目錄,path: 根目錄絕對路徑 | httpServer.setRoot(path) |
setThreadCount(cnt) | 設置響應線程數量,默認10個 | httpServer.setThreadCount(50) |
setNormalAction(actName) | 設置默認響應接口 | httpServer.setNormalAction("hello.cb") |
set404Action(actName) | 設置錯誤響應接口,不設置CBrother有默認頁面 | httpServer.set404Action("404.cb") |
setOutTime(t) | 設置請求超時時間,默認10秒,單位為秒 | httpServer.setOutTime(3) |
setMaxReqDataLen(len) | 設置客戶機發送的最大請求數據長度,默認500K,參數單位為(字節) | httpServer.setMaxReqDataLen(1024 * 1024) |
openLog() | 打開日志,在webroot平級建立log目錄默認關閉,建議打開 | httpServer.openLog() |
closeFileService() | 關閉文件下載服務錄 | httpServer.closeFileService() |
setHttpsCertFile(CRT_PATH,KEY_PATH) | 設置https證書 | httpServer.setHttpsCertFile("e:/a.crt","e:/a.key") |
setWebSocketModule(name,wsmodule) | 設置websocket模塊來支持websocket協議 | httpServer.setWebSocketModule(name,wsmodule) |
getMIME() | 獲取服務目前支持的mime格式,返回一個map對象,key為擴展,value為類型 | var map = httpServer.getMIME() |
addMIME(ext,type) | 添加mime類型,ext: 擴展名 | httpServer.addMIME("txt"); |
removeMIME(ext) | 移除mime類型,ext為擴展名 | httpServer.removeMIME("txt"); |
除了stopServer,其他接口都應該在startServer之前調用。
響應類必須要有function DoAction(request,respon)函數,如果沒有會注冊失敗。
這個方法會被多線程調用,如果要訪問公共資源需要加鎖。
函數 | 描述 | 用法 |
getUrl() | 獲取請求的路徑 | var url = request.getUrl() |
getProperty(name) | 獲取HTTP頭里的參數 | var v = request.getProperty("Content-Type") |
getAllProperty() | 獲取HTTP頭里的所有的參數,返回一個Map對象 | var proMap = request.getAllProperty() |
getParm(name) | 獲取請求鏈接?后面的參數,127.0.0.1?k=v&k1=1 | var v = request.getParm("k") |
getParmInt(name) | 獲取請求鏈接?后面的參數,強轉整形 | var v = request.getParmInt("k1") |
getAllParm() | 獲取請求鏈接?后面的所有參數,返回一個Map對象 | var parmMap = request.getAllParm() |
getData() | 獲取請求post的數據 | var data = request.getData() |
getRemoteIP() | 獲取客戶機IP | var ip = request.getRemoteIP() |
getMethod() | 獲取請求類型,"GET"或"POST" | var method = request.getMethod() |
getCookieCount() | 獲取cookie數量 | var cnt = request.getCookieCount() |
getCookie(index) | 根據索引獲取cookie,返回cookie對象 | var cookie = request.getCookie(0) |
getCookie(cookieName) | 根據名字獲取cookie,如果有重名的cookie返回第一個 | var cookie = request.getCookie("userName") |
getFormData() | 獲取form表單數據,當客戶端以form表單格式提交才返回FormData對象,否則返回null | var formdata = request.getFormData() |
isKeepAlive() | 客戶端請求是否為保持連接狀態,true為保持連接,false為立即關閉 | var isKeep = request.isKeepAlive() |
isHttps() | 客戶端請求是否為https,true為https,false為http | var isKeep = request.isHttps() |
函數 | 描述 | 用法 |
addProperty(key,value) | 添加http頭數據,必須為字符串 | respon.addProperty("Access-Control-Allow-Origin","*") |
addCookie(cookie) | 添加cookie | respon.addCookie(new Cookie("aaa","bbb")) |
setStatus(status) | 設置返回http狀態 | respon.setStatus(200) |
write(data) | 寫入要返回數據 | respon.write("hello") |
flush() | 發送數據 | respon.flush() |
在日常開發中,服務器需要通過cookie來判別客戶機身份,CBrother提供了Cookie類來描述http頭中的Set-Cookie與Cookie字段
函數 | 描述 | 用法 |
setName(name) | 設置cookie的名稱 | myCookie.setName("aaa") |
setValue(v,pwd) | 設置cookie的字段,pwd不傳為明文,傳入密碼會增加一個簽名 | myCookie.setValue("bbb") |
getName() | 獲取cookie的名稱 | var name = myCookie.getName() |
getValue(pwd) | 獲取cookie的字段,pwd不傳返回原文,傳入密碼會檢查簽名 | var cookie = myCookie.getValue() |
setPath(path) | 設置cookie的有效路徑,不設置默認為'/' | myCookie.setPath("/aa/cc/") |
setDomain(domain) | 設置cookie的域,默認為空 | myCookie.setDomain("cbrother.net") |
setExpires(timeSeconds) | 設置cookie有效時間,單位為秒,設置為0表示刪除cookie,默認瀏覽器關閉失效 | myCookie.setExpires(300) |
setHttpOnly(boolValue) | cookie的HttpOnly標簽,默認false | myCookie.setHttpOnly(true) |
getPath() | 獲取cookie的有效路徑 | var path = myCookie.getPath() |
getDomain() | 獲取cookie的域 | var domain = myCookie.getDomain() |
getExpires() | 獲取cookie有效時間 | var t = myCookie.getExpires() |
getHttpOnly() | 獲取cookie的HttpOnly標簽 | var httponly = myCookie.getHttpOnly() |
其中帶密碼的setValue方法會用密碼和原始明文算一個md5值作為cookie簽名,這樣保證了cookie不可被偽造,一般可以用于登陸狀態驗證。當然,在一些安全性要求比較高的場景,開發者也可以自己實現安全級別更高的cookie加密驗證方式。
通過一個段代碼來學習一下Cookie的用法:
class CookieAction
{
function DoAction(request,respon)
{
var cookieCnt = request.getCookieCount(); //獲取客戶機Cookie數量
if(cookieCnt > 0)
{
for(var i = 0 ; i < cookieCnt ; i++) //遍歷的方式可以獲取所有的Cookie,包括名字相同的Cookie
{
//客戶機已經有cookie
var cookie = request.getCookie(i);
if(cookie.getName() == "pwdCookie")
{
print cookie.getValue();
print cookie.getValue("pwd222"); //帶密碼的cookie
}
else
{
print cookie.getValue(); //明文cookie
}
}
var pwdCookie request.getCookie("pwdCookie"); //根據名字獲取Cookie,如果有同名Cookie則只返回第一個
print pwdCookie.getValue("pwd222");
}
else
{
//客戶機沒有cookie
var cookie = new Cookie("normalCookie","3333");
respon.addCookie(cookie); //添加明文cookie
cookie = new Cookie();
cookie.setName("pwdCookie");
cookie.setValue("3333","pwd222");
respon.addCookie(cookie); //添加帶密碼的cookie
}
var myTime = new Time();
respon.write("now time is:" + myTime.strftime("%Y/%m/%d %H:%M:%S"));
respon.flush();
}
}
CBrother提供了FormData類來描述form表單數據
函數 | 描述 | 用法 |
getType() | 獲取表單enctype類型,FORM_WWW_URLENCODED表示application/x-www-form-urlencoded,FORM_MULTI_PART表示multipart/form-data。在lib/htppdef里定義 | var t = form.getType() |
getTextCnt() | 獲取type不為"file"的input數量 | var cnt = form.getTextCnt() |
getText(index) | 根據坐標獲取input的value,type為file獲取不到 | var value = form.getText(0) |
getText(name) | 根據name獲取input的value,type為file獲取不到 | var value = form.getText("txt1") |
getTextName(index) | 根據坐標獲取input的name,type為file獲取不到 | var name = form.getTextName(0) |
getMultiPartCnt() | 當form的enctype為FORM_MULTI_PART時候,獲取input數量 | var cnt = form.getMultiPartCnt() |
getMultiPart(index) | 根據坐標獲取input數據,返回Multipart對象 | var data = form.getMultiPart(0) |
getMultiPart(name) | 根據name獲取input數據,返回Multipart對象 | var data = form.getMultiPart("file") |
addText(name,str) | 添加一個文本類型的input數據,name為名稱,str為內容.根HttpClientRequest向其他服務器發起請求的時候使用 | form.addText("111","111") |
addFile(name,filename,data,mime) | 添加一個文件類型的input數據,name為名稱,filename為文件名,data為文件內容string或ByteArray對象,mime為文件mime類型,不傳值會自動識別 | form.addFile("file1","222.txt","222") |
比如web前端提交的form表單結構如下:
<form method="post" action="form.cb" >
<input type="text" name="txt1">
<input type="text" name="txt2">
<input type="password" name="userpass"
<input type="radio" name="sex" value="man" checked>man
<input type="radio" name="sex" value="woman">woman
<input type="file" name="file" value="hiddenvalue" multiple="multiple"/>
<input type="submit" value="提交(提交按鈕)">
</form>
網頁展示效果如下:
因為這個表單沒有指定enctype,所以Content-Type為application/x-www-form-urlencoded,我們日常使用到的大部分表單都是這種格式。當前端post提交后服務器使用如下方式獲取表單數據
class FormAction
{
function DoAction(request,respon)
{
print request.getData(); //打印發送的數據
print "**********************分割線*******************************";
var formdata = request.getFormData();
if(formdata == null)
{
return; //只有Content-Type為application/x-www-form-urlencoded或者multipart/form-data時候才會返回FormData對象,否則返回null
}
var type = formdata.getType();
if(type == FORM_WWW_URLENCODED) //Content-Type為application/x-www-form-urlencoded類型的表單
{
var textCnt = formdata.getTextCnt();
for(var i = 0 ; i < textCnt ; i++)
{
print formdata.getTextName(i) + ":" + formdata.getText(i);
}
}
}
}
服務器輸出結果:
txt1=111&txt2=222&userpass=333&sex=man&file=111.txt
**********************分割線*******************************
txt1:111
txt2:222
userpass:333
sex:man
file:111.txt
這里需要學習一下form添加enctype=multipart/form-data時提交的報文格式
Multipart類來描述form表單在multipart/form-data數據類型下的一個input數據,這個類型的表單一般用在上傳文件
函數 | 描述 | 用法 |
getParm(name) | 獲取Multipart段落內的參數 | var parm = mulpart.getParm("name") |
getData(isByte) | 獲取Multipart的數據,isByte不傳值,默認返回字符串,傳true返回ByteArray對象 | var data = mulpart.getData() |
getDataLen() | 獲取數據長度 | var len = mulpart.getDataLen() |
isFile() | 如果是文件類型,返回true | var isfile = mulpart.isFile() |
getFileCount() | 獲取這個input上傳了幾個文件 | var cnt = mulpart.getFileCount() |
getFile(index) | 獲取文件對應數據,返回一個Multipart對象 | var filedata = mulpart.getFile(0) |
比如web前端提交的form表單結構如下:(只是添加了一個enctype="multipart/form-data")
<form method="post" action="form.cb" enctype="multipart/form-data">
<input type="text" name="txt1">
<input type="text" name="txt2">
<input type="password" name="userpass"
<input type="radio" name="sex" value="man" checked>man
<input type="radio" name="sex" value="woman">woman
<input type="file" name="file" value="hiddenvalue" multiple="multiple"/>
<input type="submit" value="提交(提交按鈕)">
</form>
網頁展示效果沒有變化如下:
添加了enctype,Content-Type變成multipart/form-data,服務器使用如下方式獲取表單數據
class FormAction
{
function DoAction(request,respon)
{
print request.getData(); //打印發送的數據
print "**********************分割線*******************************";
var formdata = request.getFormData();
if(formdata == null)
{
return; //只有Content-Type為application/x-www-form-urlencoded或者multipart/form-data時候才會返回FormData對象,否則返回null
}
var type = formdata.getType();
if(type == FORM_MULTI_PART) //Content-Type為multipart/form-data類型的表單
{
var formcnt = formdata.getMultiPartCnt();
for(var i = 0 ; i < formcnt ; i++)
{
var muldata = formdata.getMultiPart(i);
if(muldata.isFile()) //如果是上傳的文件
{
for(var j = 0 ; j < muldata.getFileCount() ; j++)
{
//上傳的文件可以在這里通過File對象把數據保存到文件里,CBrother目錄下sample/http/httpserver_form.cb里面有例子,這里只是把數據打印出來
var filedata = muldata.getFile(j);
print filedata.getParm("name") + " " + filedata.getParm("filename") + ": size=" + filedata.getDataLen() + " value=" + filedata.getData();
}
}
else
{
print muldata.getParm("name") + ":" + muldata.getData();
}
}
}
}
}
服務器運行結果如下:
------WebKitFormBoundaryw7KfcBx8f23zh8d4
Content-Disposition: form-data; name="txt1"
111
------WebKitFormBoundaryw7KfcBx8f23zh8d4
Content-Disposition: form-data; name="txt2"
222
------WebKitFormBoundaryw7KfcBx8f23zh8d4
Content-Disposition: form-data; name="userpass"
------WebKitFormBoundaryw7KfcBx8f23zh8d4
Content-Disposition: form-data; name="sex"
man
------WebKitFormBoundaryw7KfcBx8f23zh8d4
Content-Disposition: form-data; name="file"; filename="111.txt"
Content-Type: text/plain
i'am 111.txt file value!
------WebKitFormBoundaryw7KfcBx8f23zh8d4--
**********************分割線*******************************
txt1:111
txt2:222
userpass:
sex:man
file 111.txt: size=24 value=i'am 111.txt file value!
在開發過程中,我們經常會遇到與第三方服務器通信,這時候就需要主動請求第三方服務器接口,CBrother提供了HttpClientRequest來主動訪問別人提供的http和https接口
var myReq = new HttpClientRequest();
函數 | 描述 | 用法 |
setMethod(method) | 設置請求方式,"GET"或者"POST",類型字符串 | myReq.setMethod("POST") |
setUrl(url) | 設置請求地址,參數地址全路徑 | myReq.setUrl("http://127.0.0.1/hello.cb") |
addProperty(key,value) | 添加http頭,必須都為字符串 | myReq.addProperty("HOST","127.0.0.1") |
getProperty(key) | 獲取HTTP頭里的參數,返回值為字符串 | var v = myReq.getProperty("Content-Type") |
getAllProperty() | 獲取HTTP頭里的所有的參數,返回一個Map對象 | var proMap = myReq.getAllProperty() |
addParm(key,value) | 在鏈接上添加參數,必須都為字符串。會在鏈接地址后面追加?k1=v1&k2=v2 | myReq.addParm("11","121") |
getParm(key) | 獲取已經添加的參數 | var v = myReq.getParm("k") |
getAllParm() | 獲取已經添加的參數,返回一個Map對象 | var parmMap = myReq.getAllParm() |
addData(data) | 添加要提交的數據,data為要提交的數據,必須都為字符串 | myReq.addData("11221122") |
flush() | 發起請求,返回HttpClientResponse對象 | var response = myReq.flush() |
setOutTime(seconds) | 設置超時時間,單位為秒 | myReq.setOutTime(5) |
setKeepAlive(isKeep) | 設置是否保持與服務器連接,不設置默認false不保持 | myReq.setKeepAlive(true) |
reset(clearHead = true) | 還原成一個新的請求對象,僅保留網絡連接。開啟keepalive以此來復用網絡,clearHead傳值為false則不清理已經添加的http頭 | myReq.reset() |
close() | 如果開啟了keepalive,則主動關閉與服務器鏈接,不開啟沒意義 | myReq.close() |
addFormData(enctype) | 本次請求添加form表單數據 | var formdata = myReq.addFormData(FORM_WWW_URLENCODED) |
addCookie(cookie) | 添加一個cookie | myReq.addCookie(new Cookie("aaa","bbb")) |
getCookieCount() | 獲取添加cookie數量 | var cnt = myReq.getCookieCount() |
getCookie(index) | 根據索引獲取cookie | var cookie = myReq.getCookie(index) |
getCookie(cookieName) | 根據名字獲取cookie | var cookie = myReq.getCookie("cookieName") |
setProxy(proxyType,ip,port,acc,pwd) | 設置代理,proxyType為"http","https","socket5"中的一種。ip,port為代理服務器IP,如果服務器需要驗證用戶名,最后兩個參數需要寫帳號密碼 | myReq.setProxy("socket5","111.111.111.111",4646) |
var myReq = new HttpClientRequest();
myReq.setMethod("GET");
myReq.setUrl("https://www.baidu.com");
var myResponse = myReq.flush();
函數 | 描述 | 用法 |
getStatus() | 獲取狀態碼 | var staus = myResponse.getStatus() |
getProperty(name) | 獲取HTTP頭里的參數,返回值為字符串 | var v = myResponse.getProperty("Content-Type") |
getAllProperty() | 獲取HTTP頭里的所有的參數,返回一個Map對象 | var proMap = myResponse.getAllProperty() |
getData() | 獲取返回的數據 | var data = myResponse.getData() |
getCookieCount() | 獲取cookie數量 | var cnt = myResponse.getCookieCount() |
getCookie(index) | 根據索引獲取cookie對象 | var cookie = myResponse.getCookie(0) |
getCookie(cookieName) | 根據名字獲取cookie對象 | var cookie = myResponse.getCookie("cookieName") |
例子:
import CBHttp.code
function main(parm)
{
var myReq = new HttpClientRequest();
myReq.setMethod("GET");
myReq.setUrl("https://www.baidu.com");
var myRes = myReq.flush();
print myRes.getStatus();
print myRes.getData();
}
結果:
200
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
import CBHttp.code
import lib/httpdef
function main(parm)
{
var myReq = new HttpClientRequest();
myReq.setMethod(HTTP_GET);
myReq.setUrl("http://www.cbrother.net");
myReq.setKeepAlive(true); //開啟keepalive,如果沒有這一句默認是請求完畢后自動關閉連接
var myRes = myReq.flush();
print myRes.getStatus();
print myRes.getData();
myReq.reset(); //重置請求對象,僅保留網絡連接,如果因為超時連接被服務器主動關閉,那么第二次請求會重新創建連接
myReq.setMethod(HTTP_GET);
myReq.setUrl("http://www.cbrother.net/doc.html");
var myRes = myReq.flush(); //第二次請求沒有開啟keepalive,所以這句執行完后連接就會關閉了
print myRes.getStatus();
print myRes.getData();
}
function main(parm)
{
var myReq = new HttpClientRequest();
myReq.setMethod("GET");
myReq.setUrl("https://www.baidu.com");
myReq.addCookie(new Cookie("aaa","bbb")); //本次請求添加cookie
var myRes = myReq.flush();
var cookieCnt = myRes.getCookieCount(); //服務器返回的cookie信息
for(var i = 0 ; i < cookieCnt ; i++)
{
var cookie = request.getCookie(i);
print cookie.getName();
print cookie.getValue();
}
}
import lib/httpdef
function main(parm)
{
var clientReq = new HttpClientRequest();
clientReq.setMethod(HTTP_POST);
clientReq.setUrl("http://127.0.0.1:8003/form.cb");
var formdata = clientReq.addFormData(FORM_MULTI_PART); //也可以用FORM_WWW_URLENCODED,FORM_WWW_URLENCODED類型不支持上傳文件
formdata.addText("111","111");
formdata.addText("222","222");
formdata.addFile("file","111.txt","222");
formdata.addFile("file","222.txt","34567");
clientReq.flush();
}
如果需要在一臺服務上同時布置多個網站,則需要在調用HttpServer下面幾個接口時第二個參數傳入網站的域名即可
函數 | 描述 | 用法 |
setRoot(path,domain) | 設置域名跟目錄 | httpServer.setRoot(path,"www.cbrother.net") |
setNormalAction(actName,domain) | 設置域名默認響應接口 | httpServer.setNormalAction("hello.cb","www.cbrother.net") |
set404Action(actName,domain) | 設置域名錯誤界面接口,不設置有默認頁面 | httpServer.set404Action("404.cb","www.cbrother.net") |
setHttpsCertFile(CRT_PATH,KEY_PATH,domain) | 設置https證書,如果設置了證書,該域名才可支持https協議 |
*請認真填寫需求信息,我們會在24小時內與您取得聯系。