1 module app.http;
2 
3 
4 
5 import hunt.net;
6 import hunt.raft;
7 
8 import app.raft;
9 import std.string;
10 import std.conv;
11 import std.format;
12 
13 
14 
15 enum RequestMethod
16 {
17 	METHOD_GET = 0, 
18 	METHOD_SET = 1
19 };
20 
21 struct RequestCommand
22 {
23 	RequestMethod Method;
24 	string		  Key;
25 	string		  Value;
26 	size_t		  Hash;
27 };
28 
29 
30 enum MAX_HTTP_REQUEST_BUFF = 4096;
31 
32 alias Raft = app.raft.Raft;
33 
34 class HttpBase 
35 {
36 	this(NetSocket sock, Raft raft )
37 	{
38         this.sock = sock;
39 		this.raft = raft;
40         sock.handler((in ubyte[] data){
41 			onRead(data);
42 		});
43         sock.closeHandler((){
44 			onClose();
45 		});
46 
47 
48 	}
49 
50 	bool is_request_finish(ref bool finish, ref string url , ref string strbody)
51 	{
52 		import std.typecons : No;
53 		
54 		string str = cast(string)buffer;
55 		long header_pos = indexOf(str , "\r\n\r\n");
56 		
57 		if( header_pos == -1)
58 		{
59 			finish = false;
60 			return true;
61 		}
62 		
63 		string strlength = "content-length: ";
64 		int intlength = 0;
65 		long pos = indexOf(str , strlength , 0 , No.caseSensitive);
66 		if( pos != -1)
67 		{
68 			long left = indexOf(str , "\r\n" , cast(size_t)pos);
69 			if(pos == -1)
70 				return false;
71 			
72 			strlength = cast(string)buffer[cast(size_t)(pos + strlength.length) .. cast(size_t)left];
73 			intlength = to!int(strlength);
74 		}
75 		
76 		
77 		if(header_pos + 4 + intlength == buffer.length)
78 		{
79 			finish = true;
80 		}
81 		else
82 		{
83 			finish = false;
84 			return true;
85 		}
86 		
87 		long pos_url = indexOf(str , "\r\n");
88 		if(pos_url == -1)
89 			return false;
90 		
91 		auto strs = split(cast(string)buffer[0 .. cast(size_t)pos_url]);
92 		if(strs.length < 3)
93 			return false;
94 		
95 		url = strs[1];
96 		strbody = cast(string)buffer[cast(size_t)(header_pos + 4) .. $];
97 		
98 		return true;
99 	}
100 
101 	bool do_response(string strbody)
102 	{
103 		auto res = format("HTTP/1.1 200 OK\r\nServer: kiss\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s"
104 			, strbody.length , strbody);
105 	 	sock.write(res);
106 		return true;
107 	}
108 
109 	bool process_request(string url , string strbody)
110 	{
111 		string path;
112 		long pos = indexOf(url , "?");
113 		string[string] params;
114 		if(pos == -1){
115 			path = url;
116 		}
117 		else{
118 			path = url[0 .. pos];
119 			auto keyvalues = split(url[pos + 1 .. $] , "&");
120 			foreach( k ; keyvalues)
121 			{
122 				auto kv = split(k , "=");
123 				if(kv.length == 2)
124 					params[kv[0]] = kv[1];
125 			}
126 		}
127 
128 		if(path == "/get")
129 		{
130 			auto key = "key" in params;
131 			if(key == null || key.length == 0)
132 				return do_response("params key must not empty");
133 
134 			RequestCommand command = { Method:RequestMethod.METHOD_GET , Key: *key , Hash:this.toHash()};
135 			raft.readIndex(command , this);
136 			return true;
137 		}
138 		else if(path == "/set")
139 		{
140 
141 			auto key = "key" in params;
142 			auto value = "value" in params;
143 			if(key == null || value == null || key.length == 0)
144 				return do_response("params key  must not empty or value not exist");
145 
146 			RequestCommand command = { Method:RequestMethod.METHOD_SET , Key: *key ,Value : *value , Hash:this.toHash()};
147 			raft.propose(command , this);
148 			return true;
149 		}
150 		else if(path == "/add")
151 		{
152 			
153 
154 			auto nodeID = "ID" in params;
155 			auto Context = "Context" in params;
156 			if(nodeID == null || nodeID.length == 0 || Context.length == 0 || Context == null)
157 				return do_response("ID or Context must not empty");
158 
159 			ConfChange cc = { NodeID : to!ulong(*nodeID) , Type : ConfChangeType.ConfChangeAddNode ,Context:*Context };
160 			raft.proposeConfChange(cc);
161 			return do_response("have request this add conf");
162 			
163 		}
164 		else if(path == "/del")
165 		{
166 			auto nodeID = "ID" in params;
167 			if(nodeID == null || nodeID.length == 0)
168 				return do_response("ID must not empty");
169 			ConfChange cc = { NodeID : to!ulong(*nodeID) , Type : ConfChangeType.ConfChangeRemoveNode };
170 			raft.proposeConfChange(cc);
171 			return do_response("have request this remove conf");
172 		}
173 		else
174 		{
175 			return do_response("can not sovle " ~ path);
176 		}
177 
178 	}
179 
180 
181 
182 	void onRead(in ubyte[] data)
183 	{
184 		buffer ~= data;
185 		bool finish;
186 		string strurl;
187 		string strbody;
188 		if(!is_request_finish(finish ,strurl,strbody ))
189 			return ;
190 
191 		if(finish)
192 		{
193 			process_request(strurl , strbody);
194 		}
195 		else if(buffer.length >= MAX_HTTP_REQUEST_BUFF)
196 		{
197 			return ;
198 		}
199 
200 	}
201 
202 	void close()
203 	{
204 		sock.close();
205 	}
206 
207 	void onClose() {
208 		raft.delPropose(this);
209 	}
210 
211     ubyte[]         buffer;
212 	NetSocket       sock;
213 	Raft			raft;
214 }
215