-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproj2_client.cc
415 lines (360 loc) · 14.3 KB
/
proj2_client.cc
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
https://powcoder.com
代写代考加微信 powcoder
Assignment Project Exam Help
Add WeChat powcoder
// Modified from CSE422 FS09
// Modified from CSE422 FS12
// Simple HTTP client for CSE422 F13 lab 02.
#include <climits>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <netdb.h>
#include <string>
#include "proj2_client.h"
#include "HTTP_Request.h"
#include "HTTP_Response.h"
#include "TCP_Socket.h"
using namespace std;
/**
* Simple HTTP client program which sends/receives an HTTP request/response from a
* server. If the proxy url is identified, it will send its request there.
*
* @param argc, argv The command line arguments
*/
int main(int argc, char* argv[]) {
char* server_addr = NULL;
char* proxy_addr = NULL;
URL* server_url = NULL;
URL* proxy_url = NULL;
HTTP_Response* response = NULL;
TCP_Socket client_sock;
string print_buffer;
char* client_id_c = NULL;
string client_id;
parse_args(argc, argv, &server_addr, &proxy_addr, &client_id_c);
client_id = string(client_id_c);
parse_parsed_addrs(server_addr, proxy_addr, server_url, proxy_url, &client_sock);
try {
send_GET_request(&client_sock, server_url, client_id);
string response_body;
response = recv_response_header(&client_sock, response_body);
recv_response_body(&client_sock, server_url, response, response_body);
} catch (string msg) {
cerr << msg << endl;
// Clean up resources
if (server_url != NULL) delete server_url;
if (proxy_url != NULL) delete proxy_url;
if (response != NULL) delete response;
client_sock.Close();
}
// If the response is not OK, something is wrong.
// However, we still downloaded the content, because even if the response
// is not 200, the server still replies with an error page (403, 404 ...)
if(response->get_status_code() != 200) {
cerr << response->get_status_code() << " "
<< response->get_status_desc() << endl;
}
// everything's done.
client_sock.Close();
delete response;
delete server_url;
if(proxy_addr != NULL) {
delete proxy_url;
}
return 0;
}
/**
* Parses the addr strings to create the corrsponding URL object when
* appropriate and the corresponding socket. Note that the server_addr must
* be non-NULL as the HTTP GET will need to end up there, regardless of the
* proxy. This function will print an error message and exit if given url
* strings are not formatted correctly, no server_url is given, or if the
* socket cannot be created.
*
* @param[in] server_addr A string indicating the server address.
* @param[in] proxy_addr A string indicating the proxy address.
* @param[out] server_url The location where the serverl URL will be placed
* when created.
* @param[out] proxy_url The location where the proxy URL will be placed, if
* created.
* @param[out] client_sock TCP_Socket that will be Connect()-ed to the
* server/proxy.
*/
void parse_parsed_addrs(char *server_addr, char *proxy_addr, URL *&server_url,
URL *&proxy_url, TCP_Socket *client_sock) {
// Must have a server to get data from
if(server_addr == NULL) {
cout << "You did not specify the host address." << endl;
help_message("client", cout);
exit(1);
}
server_url = URL::parse(server_addr);
if(server_url == NULL) { // If URL parsing is failed
cout << "Unable to parse host address: " << server_addr << endl;
cout << endl;
help_message("client", cout);
exit(1);
}
// Proxy is an otional argument
// If a proxy URL is specified, the client connects to the proxy and
// downlods from the proxy. Otherwise, the client connects to the host
// and downloads from the host.
if (proxy_addr != NULL) {
proxy_url = URL::parse(proxy_addr);
if(proxy_url == NULL) {
cout << "Unable to parse proxy address: " << proxy_addr << endl;
cout << endl;
help_message("client", cout);
exit(1);
}
if(proxy_url->is_port_defined() == false) {
cout << "Proxy port is not defined, assumed to be 8080" << endl;
proxy_url->set_port(8080);
}
}
// TCP_Socket class to handle TCP communications.
if(proxy_addr == NULL) {
// If proxy is not specified, connect to the host directly.
try{
// Connect to the target server.
client_sock->Connect(*server_url);
}
// Give up if sock is not created correctly.
catch(string msg) {
cout << msg << endl;
cout << "Unable to connect to server: "
<< server_url->get_host() << endl;
delete server_url;
exit(1);
}
} else { // proxy is specified, connect to proxy
try {
// Connect to the proxy, instead of server
client_sock->Connect(*proxy_url);
}
// Give up if sock is not created correctly.
catch(string msg) {
cout << msg << endl;
cout << "Unable to connect to proxy: "
<< proxy_url->get_host() << endl;
delete server_url;
delete proxy_url;
exit(1);
}
}
}
/**
* Sends a GET request via the passed TCP_Socket passed according to the given
* URL.
*
* @param client_sock Socket to send the message on.
* @param server_url Denotes the HTTP GET's final destination and what to grab.
*
* @throws string Error message if the TCP_Socket throws an error.
*/
void send_GET_request(TCP_Socket *client_sock, URL *server_url, string &client_id) {
string print_buffer;
HTTP_Request *request;
// Send a GET request for the specified file.
// No matter connecting to the server or the proxy, the request is
// alwasy destined to the server.
stringstream ss;
ss << server_url->get_path(); // << "?" << server_url->get_query();
request = HTTP_Request::create_GET_request(ss.str());
request->set_host(server_url->get_host());
// This request is non-persistent.
request->set_header_field("Connection", "close");
// For real browsers, If-Modified-Since field is always set.
// if the local object is the latest copy, the browser does not
// respond the object.
request->set_header_field("If-Modified-Since", "0");
request->set_header_field("ClientID", client_id);
try {
request->send(*client_sock);
}
catch(string msg) {
delete request; // clean up local resource
throw msg; // warn others
}
// output the request
cout << "Request sent..." << endl;
cout << "=========================================================="
<< endl;
request->print(print_buffer);
cout << print_buffer.substr(0, print_buffer.length() - 4) << endl;
cout << "=========================================================="
<< endl;
delete request; // We do not need it anymore
}
/**
* Receives the response header (and part of the body).
*
* @param[in] client_sock The socket connected to the server.
* @param[out] response_body A string to hold the part of the body received when the
* headers are read.
* @return The HTTP_Response built from the received headers.
* @throws string Error message if the TCP_Socket throws an error.
*/
HTTP_Response *recv_response_header(TCP_Socket *client_sock, string &response_body) {
string print_buffer, response_header;
HTTP_Response *response;
// Read enough of the server's response to get all of the headers,
// then have that response interpreted so we at least know what
// happened.
client_sock->read_header(response_header, response_body);
response = HTTP_Response::parse(response_header.c_str(),
response_header.length());
if(response != NULL) {
// output the response header
cout << endl << "Response header received" << endl;
cout << "=========================================================="
<< endl;
response->print(print_buffer);
cout << print_buffer.substr(0, print_buffer.length() - 4) << endl;
cout << "=========================================================="
<< endl;
} else {
throw string("Unable to parse the response header.");
}
return response;
}
/**
* Receive the response body from the given socket and writes it to a file.
*
* @param client_sock The socket connected to the client.
* @param server_url The url containing the file name (to open the corresponding file)
* @param response The HTTP response header containing information about the transfer
* encoding.
* @param response_body A string containing a part of the body already received.
* @throws string Error message if the TCP_Socket throws an error.
*/
void recv_response_body(TCP_Socket *client_sock, URL *server_url, HTTP_Response *response,
string &response_body) {
FILE* out = NULL;
// Open a local copy in which to store the file.
out = open_local(server_url);
// check
if(!out) {
cout << "Error opening local copy for writing." << endl;
// clean up if failed
exit(1);
}
cout << endl << "Downloading rest of the file ... " << endl;
int bytes_written = 0, bytes_left, chunk_len;
int total_data;
if(response->is_chunked() == false) {
// none-chunked encoding transfer does not split the data into
// chunks. The header specifies a content_length. The client knows
// exactly how many data it is expecting. The client keeps receiving
// the response until it gets the amount.
cout << "Default encoding transfer" << endl;
cout << "Content-length: " << response->get_content_len() << endl;
bytes_left = response->get_content_len();
do {
// If we got a piece of the file in our buffer for the headers,
// have that piece written out to the file, so we don't lose it.
fwrite(response_body.c_str(), 1, response_body.length(), out);
bytes_written += response_body.length();
bytes_left -= response_body.length();
//cout << "bytes written:" << bytes_written << endl;
//cout << "data gotten:" << response_body.length() << endl;
response_body.clear();
try {
// Keeps receiving until it gets the amount it expects.
client_sock->read_data(response_body, bytes_left);
}
catch(string msg) {
// something bad happend, clean up
fclose(out);
// warn the others
throw msg;
}
} while (bytes_left > 0);
}
else { // chunked encoding
cout << "Chunked encoding transfer" << endl;
// As mentioned above, read_header function already split the
// data from the header from us. The beginning of this respnse_data
// now holds the first chunk size.
total_data = 0;
int chunk_len;
do {
// Get chunk size
chunk_len = get_chunk_size(response_body);
if (chunk_len < 0) {
try {
client_sock->read_line(response_body);
} catch (string msg) {
// something bad happend, clean up
fclose(out);
// warn the others
throw msg;
}
chunk_len = get_chunk_size(response_body);
}
if (chunk_len < 0) {
fclose(out);
throw string("Could not get chunk size!");
}
cout << "chunk length: " << chunk_len<< endl;
// Loop through read to get entire chunk
bytes_left = chunk_len+2 - response_body.length();
try {
while (bytes_left > 0)
bytes_left -= client_sock->read_data(response_body, bytes_left);
} catch (string msg) {
// something bad happend, clean up
fclose(out);
// warn the others
throw msg;
}
// Write chunk to file
fwrite(response_body.c_str(), 1, chunk_len, out);
bytes_written += chunk_len;
total_data += chunk_len;
// Remove chunk and ending \r\n
response_body = response_body.substr(chunk_len+2,
response_body.length() - chunk_len - 2);
} while (chunk_len > 0);
}
fclose(out);
cout << endl << "Download complete (" << bytes_written;
cout << " bytes written)" << endl;
// This checks if the chunked encoding transfer mode is downloading
// the contents correctly. -- THIS IS BOGUS
if((total_data != bytes_written) && response->is_chunked() == true) {
cout << "WARNING" << endl
<< "Data received does not match chunk size." << endl;
}
}
/**
* For a given data, try to extract the chunk_len. Fails if the chunk size is
* not in the string.
*
* @param data The data as string
* @return The extracted chunk length if possible, or -1 otherwise.
*/
// For the client, it needs to remove the chunk_len from the data string
// because the client is storing the data as a file. The chunk length
// is no longer needed anymore. However, for proxies, they need to keep the
// chunk length, so that the forwarded response body can be decoded/received
// by the clients.
int get_chunk_size(std::string &data) {
int chunk_len = -1; // The value we want to obtain
int chunk_len_str_end; // The var to hold the end of chunk length string
std::stringstream ss; // For hex to in conversion
chunk_len_str_end = data.find("\r\n"); // Find the first CLRF
if (chunk_len_str_end != std::string::npos) {
// take the chunk length string out
std::string chunk_len_str = data.substr(0, chunk_len_str_end);
// convert the chunk length string hex to int
ss << std::hex << chunk_len_str;
ss >> chunk_len;
// reorganize the data
// remove the chunk length string and the CLRF
data = data.substr(chunk_len_str_end + 2, data.length() - chunk_len_str_end - 2);
}
return chunk_len;
}