——聲明,腦殘人士遠離,本博客的核心不是if-else+前綴,而是如何通過URL協議處理框架定義私有協議
URI與URL的區別
1
|
URI (uniform resource identifier)統一資源標志符;URL(uniform resource location )統一資源定位符(或統一資源定位器);URI是一個相對來說更廣泛的概念,URL是URI的一種,是URI命名機制的一個子集,可以說URI是抽象的,而具體要使用URL來定位資源。URI指向的一般不是物理資源路徑,而是整個系統中的映射后的資源標識符。URL是Internet上用來描述信息資源的字符串,主要用在各種WWW客戶程序和服務器程序上。采用URL可以用一種統一的格式來描述各種信息資源,包括文件、服務器的地址和目錄等。 |
一.先來序言一段
我們習慣了http
1
|
URL url= new URL(<a href= "http://www.apptest.com:8080/test/ios.php" >http://www.apptest.com: 8080 /test/ios.php</a>); |
我們也要習慣
當然,我們還要讓URL習慣我們
1
|
"https" , "ftp" , "mailto" , "telnet" , "file" , "ldap" , "gopher" , "jdbc" , "rmi" , "jndi" , "jar" , "doc" , "netdoc" , "nfs" , "verbatim" , "finger" , "daytime" , "systemresource" |
1
|
URL url= new URL( "oschina://www.apptest.com:8080/test/ios.php" ); |
如果不習慣,總會出現如下異常
1
|
java.net.MalformedURLException: unknown protocol |
在Android瀏覽器使用Ajax時也會不支持沒有定義的過的協議。
二.協議的自定義的理解
協議:在編程的世界里,協議本身就是一套Input/ouput約束規則,因此,我們確切的協議應該圍繞I/O展開的,所以,這里的協議可以稱為I/O協議。
協議發起方:request
協議響應方:response
協議成立的條件是:request和reponse認可同一套協議,并按照協議約束進行通信。
三.自定義協議與URL的關系
在java中,自定義協議一定需要用URL嗎?
答案是否定的。
事實上,圍繞I/O,我們的規則定義完全有我們本身掌握,并沒有說離開URL地球不轉了,Java要毀滅了。
為什么使用URL類來自定義協議?
答案是因為URL是一套成熟的協議通信處理框架。
這里說的自定義URL協議,實質上更多的是通過已有的規則進行擴充協議。
四.URL自定義私有協議實戰
我們知道,自定義協議需要Response 和Request,雙方需要充理解對方的協議。這里為了方便起見,我們使用Http協議服務器來作為Response。
這里我們使用了Ngnix服務器+PHP+FastCGI來構建Reponse,部署代碼如下
1.定義Response
1
2
3
4
5
6
|
<?php $raw_post_data = file_get_contents ( 'php://input' , 'r' ); echo "-------\$_POST------------------\n<br/>" ; echo var_dump( $_POST ) . "\n" ; echo "-------php://input-------------\n<br/>" ; echo $raw_post_data . "\n<br/>" ; $rs = json_encode( $_SERVER ); file_put_contents ( 'text.html' , $rs ); echo '寫入成功' ; |
2.定義Request
2.1實現URLStreamHandlerFactory工廠,主要用來產生協議處理器
1
2
3
4
5
6
7
|
public class EchoURLStreamHandlerFactory implements URLStreamHandlerFactory { public URLStreamHandler createURLStreamHandler(String protocol){ //通過這里的分流處理不同的schema請求,當然腦殘人士認為這里才是核心代碼,URL是一套協議處理框架,如果if-else就是核心,是不是oracle要倒閉 if (protocol.equals( "echo" ) || protocol.equals( "oschina" )) { return new EchoURLStreamHandler(); //實例化協議處理Handler } return null ; }} |
2.2實現URLStreamHandler,主要作用是生成協議對應的連接器
1
2
3
4
5
|
public class EchoURLStreamHandler extends URLStreamHandler { @Overrideprotected URLConnection openConnection(URL u) throws IOException { return new EchoURLConnection(u); //在這里我們也可以進行相應的分流} } |
2.3 實現URLConnection,作用是協議通信規則的自定義,這里我們使用HTTP協議作為通信規則,我們這里仿制http協議請求
(以下才是核心代碼,這里借用的http協議,當然你可以用websocket,smtp,ftp各種協議進行交互,而不是腦殘人士讓我承認的 if-else+URL前綴)
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
|
public class EchoURLConnection extends URLConnection { private Socket connection = null ; public final static int DEFAULT_PORT = 80 ; public EchoURLConnection(URL url) { super (url);} public synchronized InputStream getInputStream() throws IOException { if (!connected) {connect(); } return connection.getInputStream(); } public synchronized OutputStream getOutputStream() throws IOException { if (!connected) {connect(); } return connection.getOutputStream(); } public String getContentType() { return "text/plain" ; } public synchronized void connect() throws IOException { if (!connected) { int port = url.getPort(); if (port < 0 || port > 65535 )port = DEFAULT_PORT; this .connection = new Socket(url.getHost(), port); // true表示關閉Socket的緩沖,立即發送數據..其默認值為false// 若Socket的底層實現不支持TCP_NODELAY選項,則會拋出SocketExceptionthis.connection.setTcpNoDelay( true ); // 表示是否允許重用Socket所綁定的本地地址this.connection.setReuseAddress(true); // 表示接收數據時的等待超時時間,單位毫秒..其默認值為0,表示會無限等待,永遠不會超時 // 當通過Socket的輸入流讀數據時,如果還沒有數據,就會等待 // 超時后會拋出SocketTimeoutException,且拋出該異常后Socket仍然是連接的,可以嘗試再次讀數據this.connection.setSoTimeout(30000); // 表示當執行Socket.close()時,是否立即關閉底層的Socket // 這里設置為當Socket關閉后,底層Socket延遲5秒后再關閉,而5秒后所有未發送完的剩余數據也會被丟棄 // 默認情況下,執行Socket.close()方法,該方法會立即返回,但底層的Socket實際上并不立即關閉 // 它會延遲一段時間,直到發送完所有剩余的數據,才會真正關閉Socket,斷開連接 // Tips:當程序通過輸出流寫數據時,僅僅表示程序向網絡提交了一批數據,由網絡負責輸送到接收方 // Tips:當程序關閉Socket,有可能這批數據還在網絡上傳輸,還未到達接收方 // Tips:這里所說的"未發送完的剩余數據"就是指這種還在網絡上傳輸,未被接收方接收的數據this.connection.setSoLinger(true, 5); // 表示發送數據的緩沖區的大小this.connection.setSendBufferSize(1024); // 表示接收數據的緩沖區的大小this.connection.setReceiveBufferSize(1024); // 表示對于長時間處于空閑狀態(連接的兩端沒有互相傳送數據)的Socket,是否要自動把它關閉,true為是 // 其默認值為false,表示TCP不會監視連接是否有效,不活動的客戶端可能會永久存在下去,而不會注意到服務器已經崩潰this.connection.setKeepAlive(true); // 表示是否支持發送一個字節的TCP緊急數據,socket.sendUrgentData(data)用于發送一個字節的TCP緊急數據 // 其默認為false,即接收方收到緊急數據時不作任何處理,直接將其丟棄..若用戶希望發送緊急數據,則應設其為true // 設為true后,接收方會把收到的緊急數據與普通數據放在同樣的隊列中this.connection.setOOBInline(true); // 該方法用于設置服務類型,以下代碼請求高可靠性和最小延遲傳輸服務(把0x04與0x10進行位或運算) // Socket類用4個整數表示服務類型// 0x02:低成本(二進制的倒數第二位為1) // 0x04:高可靠性(二進制的倒數第三位為1)// 0x08:最高吞吐量(二進制的倒數第四位為1) // 0x10:最小延遲(二進制的倒數第五位為1)this.connection.setTrafficClass(0x04 | 0x10); // 該方法用于設定連接時間,延遲,帶寬的相對重要性(該方法的三個參數表示網絡傳輸數據的3項指標) // connectionTime--該參數表示用最少時間建立連接 // latency---------該參數表示最小延遲 // bandwidth-------該參數表示最高帶寬// 可以為這些參數賦予任意整數值,這些整數之間的相對大小就決定了相應參數的相對重要性/ // 如這里設置的就是---最高帶寬最重要,其次是最小連接時間,最后是最小延遲this.connection.setPerformancePreferences(2, 1, 3);this.connected = true;StringBuilder sb = new StringBuilder();sb.append("POST " + url.getPath() + " HTTP/1.1 \r\n"); //if(url.getPort()<0 || url.getPort()>65536){sb.append("Host:").append(url.getHost()).append("\r\n");}else{sb.append("Host:").append(url.getHost()).append(":").append(url.getPort()).append("\r\n");}sb.append("Connection:keep-alive\r\n");sb.append("Date:Fri, 22 Apr 2016 13:17:35 GMT\r\n");sb.append("Vary:Accept-Encoding\r\n");sb.append("Content-Type: application/x-www-form-urlencoded,charset=utf-8\r\n");sb.append("Content-Length: ").append("name=zhangsan&password=123456".getBytes("UTF-8").length).append("\r\n");sb.append("\r\n");this.connection.getOutputStream().write(sb.toString().getBytes("UTF-8"));}}public synchronized void disconnect() throws IOException {if (connected) {this.connection.close();this.connected = false;}}} |
在這里,協議定義已經完成。
我們測試代碼如下
嘗試連接 oschina://localhost:8080/test/ios.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
URL.setURLStreamHandlerFactory( new EchoURLStreamHandlerFactory()); // URLConnection.setContentHandlerFactory(new EchoContentHandlerFactory()); URL url= new URL( "oschina://localhost:8080/test/ios.php" ); EchoURLConnection connection=(EchoURLConnection)url.openConnection(); connection.setDoOutput( true );connection.setDoInput( true ); PrintWriter pw = new PrintWriter( new OutputStreamWriter(connection.getOutputStream())); pw.write( "name=zhangsan&password=123456" );pw.flush(); InputStream stream = connection.getInputStream(); int len = - 1 ; byte [] buf = new byte [ 256 ]; while ((len=stream.read(buf, 0 , 256 ))>- 1 ) { String line = new String(buf, 0 , len); if (line.endsWith( "\r\n0\r\n\r\n" )&&len< 256 ) { //服務器返回的是Transfer-chunked編碼,\r\n0\r\n\r\n表示讀取結束了,chunked編碼解析:http://dbscx.iteye.com/blog/830644 line = line.substring(0, line.length()-"\r\n0\r\n\r\n".length()); System.out.println(line); break ; } else { System.out.println(line); } } pw.close(); stream.close(); |
運行結果
結果說明,協議確實定義成功了
當然,如上數據解析不符合我們的要求,因為是chunked編碼信息,如何解析符合要求有,請移步:
HTTP Chunked數據編碼與解析算法
五.后話,自定義mineType解析器
java中提供了ContentHandlerFactory,用來解析mineType,我們這里制定我們自己的解析器,當然,JDK中提供的更豐富,這里所做的只是為了符合特殊需求
1
2
3
4
5
6
7
8
9
10
11
12
|
public class EchoContentHandler extends ContentHandler { public Object getContent(URLConnection connection) throws IOException { InputStream in = connection.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(in)); return br.readLine(); } public Object getContent(URLConnection connection, Class[] classes) throws IOException {InputStream in = connection.getInputStream(); for ( int i = 0 ; i < classes.length; i++) { if (classes[i] == InputStream. class ) return in; else if (classes[i] == String. class ) return getContent(connection); } return null ;}} |
用法很簡單
1
|
URLConnection.setContentHandlerFactory( new EchoContentHandlerFactory()); |