Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kevin Sooter - Sprint-Challenge--C-Web-Server #138

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
104 changes: 86 additions & 18 deletions src/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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;
}

Expand All @@ -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);
}

Expand All @@ -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;
}