diff --git a/README.md b/README.md index 32c21e3..9ff0107 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ client that will run from the command line. [cURL](https://en.wikipedia.org/wiki/CURL), which stands for "Client URL", is a command line tool that can make requests to servers, just like browsers can. You -may have been using cURL in order to test your web server implementation. +may have been using cURL in order to test your web server implementation. If you've never played around with cURL, open up a terminal window and type in @@ -48,7 +48,7 @@ curl -D - www.google.com When that command gets executed, you'll see that you get back an HTTP response with a whole bunch of HTML in the body. You just requested Google's home page, but since cURL is just a command line tool, it isn't capable of taking the HTML -in the response and rendering it. +in the response and rendering it. Your program for the Sprint Challenge will be a stripped down version of cURL that can only make GET requests. Your MVP implementation will need to be able to @@ -85,24 +85,25 @@ The steps that your client will need to execute are the following: 1. Parse the input URL. - * Your client should be able to handle URLs such as `localhost:3490/d20` and + - Your client should be able to handle URLs such as `localhost:3490/d20` and `www.google.com:80/`. Input URLs need to be broken down into `hostname`, `port`, and `path`. The `hostname` is everything before the colon (but doesn't include `http://` or `https://` if either are present), the `port` is the number after the colon ending at the backslash, and the `path` is everything after the backslash. - * Implement the `parse_url()` function, which receives the input URL and + - Implement the `parse_url()` function, which receives the input URL and tokenizes it into `hostname`, `port`, and `path` strings. Assign each of these to the appropriate field in the `urlinfo_t` struct and return it from the `parse_url()` function. - * You can use the `strchr` function to look for specific characters in a + - You can use the `strchr` function to look for specific characters in a string. You can also use the `strstr` function to look for specific - substrings in a string. + substrings in a string. -2. Construct the HTTP request. - * Just like in the web server, use `sprintf` in order to construct the +2. Construct the HTTP request. + + - Just like in the web server, use `sprintf` in order to construct the request from the `hostname`, `port`, and `path`. Requests should look like the following: @@ -119,22 +120,22 @@ The steps that your client will need to execute are the following: 3. Connect to the server. - * All of the networking logic that you'll need to connect to an arbitrary + - All of the networking logic that you'll need to connect to an arbitrary server is provided in the `lib.h` and `lib.c` files. All you have to do call the `get_socket()` function in order to get a socket that you can then - send and receive data from using the `send` and `recv` system calls. + send and receive data from using the `send` and `recv` system calls. - * Make sure that your web server implementation (built during project days 1 + - Make sure that your web server implementation (built during project days 1 & 2 from Web Server I) is running in another terminal window when testing local requests. 4. Send the request string down the socket. - * Hopefully that's pretty self-explanatory. + - Hopefully that's pretty self-explanatory. 5. Receive the response from the server and print it to `stdout`. - * The main hurdle that needs to be overcome when receiving data from a server + - The main hurdle that needs to be overcome when receiving data from a server is that we have no idea how large of a response we're going to get back. So to overcome this, we'll just keep calling `recv`, which will return back data from the server up to a maximum specified byte length on each @@ -149,8 +150,8 @@ The steps that your client will need to execute are the following: 6. Clean up. - * Don't forget to `free` any allocated memory and `close` any open file - descriptors. + - Don't forget to `free` any allocated memory and `close` any open file + descriptors. In your solution, it is essential that you follow best practices and produce clean and professional results. Schedule time to review, refine, and assess your @@ -217,20 +218,20 @@ goals: 1. Make the URL parsing logic more robust. - * The specified URL parsing logic is really brittle. The most glaring hole is + - The specified URL parsing logic is really brittle. The most glaring hole is the fact that oftentimes, URLs don't actually include the port number. In such cases, clients just assume a default port number of 80. Improve the URL parsing logic such that it can handle being passed a URL without a port - number, such as `www.google.com/`. + number, such as `www.google.com/`. - * Also improve the parsing logic so that it can receive URLs prepended with + - Also improve the parsing logic so that it can receive URLs prepended with `http://` or `https://`. Such URLs should not be treated any differently by the client, you'll just need to strip them off the input URL so that they - don't become part of the hostname. + don't become part of the hostname. 2. Implement the ability for the client to follow redirects. - * If you execute `./client google.com:80/`, you'll get back a response with a + - If you execute `./client google.com:80/`, you'll get back a response with a `301 Moved Permanently` status. There's a `Location` field in the header as well as a `href` tag in the body specifying where the client needs to be redirected. Augment your client such that when it encounters a 301 status, @@ -239,7 +240,7 @@ goals: 3. Don't have the client print out the header. - * Let's make the printing of the header of the response optional. Implement + - Let's make the printing of the header of the response optional. Implement functionality such that the client can accept a `-h` flag, and only when this flag is present do we print the response header as well. Otherwise, when printing a response, your client should just print the body of the diff --git a/src/client.c b/src/client.c index 5246a49..f545dc4 100644 --- a/src/client.c +++ b/src/client.c @@ -12,7 +12,8 @@ /** * Struct to hold all three pieces of a URL */ -typedef struct urlinfo_t { +typedef struct urlinfo_t +{ char *hostname; char *port; char *path; @@ -28,15 +29,14 @@ typedef struct urlinfo_t { urlinfo_t *parse_url(char *url) { // copy the input URL so as not to mutate the original - char *hostname = strdup(url); + char *copyUrl = strdup(url); + char *hostname; char *port; char *path; - urlinfo_t *urlinfo = malloc(sizeof(urlinfo_t)); /* We can parse the input URL by doing the following: - 1. Use strchr to find the first backslash in the URL (this is assuming there is no http:// or https:// in the URL). 2. Set the path pointer to 1 character after the spot returned by strchr. 3. Overwrite the backslash with a '\0' so that we are no longer considering anything after the backslash. @@ -45,10 +45,50 @@ urlinfo_t *parse_url(char *url) 6. Overwrite the colon with a '\0' so that we are just left with the hostname. */ - /////////////////// - // IMPLEMENT ME! // - /////////////////// + // Will strip https or http off + if (copyUrl[0] == 'h') + { + hostname = strchr(copyUrl, '/'); + hostname += 2; + } + else + { + hostname = copyUrl; + } + + // Check for path, if none will be blank + path = strrchr(hostname, '/'); + if (path) + { + *path = '\0'; + path++; + } + else + { + path = ""; + } + + // Finds the port, if none default is 80 + port = strrchr(hostname, ':'); + if (port) + { + *port = '\0'; + port++; + } + else + { + port = "80"; + } + + printf("hostname: %s\n", hostname); + printf("port: %s\n", port); + printf("path: %s\n", path); + urlinfo->hostname = strdup(hostname); + urlinfo->port = strdup(port); + urlinfo->path = strdup(path); + + free(copyUrl); return urlinfo; } @@ -68,20 +108,31 @@ int send_request(int fd, char *hostname, char *port, char *path) char request[max_request_size]; int rv; - /////////////////// - // IMPLEMENT ME! // - /////////////////// + int request_length = snprintf(request, max_request_size, + "GET /%s HTTP/1.1\n" + "Host: %s:%s\n" + "Connection: close\n" + "\n", + path, hostname, port); - return 0; + rv = send(fd, request, request_length, 0); + + if (rv < 0) + { + perror("send"); + } + + return rv; } int main(int argc, char *argv[]) -{ - int sockfd, numbytes; +{ + int sockfd, numbytes; char buf[BUFSIZE]; - if (argc != 2) { - fprintf(stderr,"usage: client HOSTNAME:PORT/PATH\n"); + if (argc != 2) + { + fprintf(stderr, "usage: client HOSTNAME:PORT/PATH\n"); exit(1); } @@ -93,9 +144,26 @@ int main(int argc, char *argv[]) 5. Clean up any allocated memory and open file descriptors. */ - /////////////////// - // IMPLEMENT ME! // - /////////////////// + urlinfo_t *urlinfo = parse_url(argv[1]); + sockfd = get_socket(urlinfo->hostname, urlinfo->port); + send_request(sockfd, urlinfo->hostname, urlinfo->port, urlinfo->path); + + while ((numbytes = recv(sockfd, buf, BUFSIZE - 1, 0)) > 0) + { + fprintf(stdout, "%s", buf); + } + + // Free and close all allocated memory + free(urlinfo->hostname); + free(urlinfo->port); + + if (urlinfo->path != NULL) + { + free(urlinfo->path); + } + free(urlinfo); + + close(sockfd); return 0; }