Homework 3 Part 2: A Barebones HTTP/1.1 Client
This assignment is not optional and is due at 11:59 pm on Monday April 13.
You can access the homework starter code here.
Make sure to read the homework before starting. We have incorporated updated autograder sumbission and testing instructions.
In this programming exercise, you will create a barebones web client.
While python includes a basic http client module http.client
, this
assignment will serve as a learning experience for translating a
protocol into an implementation. One of your deliverables will be a
client that behaves identically to the command line program curl
given
the -f
flag (curl -f
). To do this, you will follow the HTTP/1.1
specification to build a program that can generate a request with the
GET
method and process the subsequent HTTP reply. Additionally, this
client must also support SSL and verify the certificate of pages that
you connect to.
There are three goals of this assignment:
- Introduce you to using POSIX sockets for network communication in an application.
- Provide experience with correctly implementing a network protocol given a specification.
- Exercise your ability to understand test edge-cases in network protocols.
The submission requirements and the grading for this assignment are created with these goals in mind. In short, each of these three goals shapes this assignment as follows:
- Edge-cases related to POSIX sockets will be tested for. This
means that the auto-grader will be testing for both IPv4/IPv6 DNS
support and for correct behavior given partial data with the
send
andrecv
system calls. - You are required to build a client that is strictly compliant with the HTTP protocol specification (See the RFC). As such, edge-cases related to atypical messages will be tested, including spacing, capitalization, and line-wrapping. In general, the HTTP standard is permissive. There are many different valid ways to format requests, and your client is expected to accept any valid response.
- To demonstrate your understanding of the HTTP protocol specification, you are required to create new test cases and include both the test cases and a description of what aspect of compliance to the protocol the test case covers.
The rest of this assignment description is organized into the following sections:
Client functionality
As seen in class, HTTP is a stateless request-response protocol that consists
of an initial line, zero or more headers, and zero or more bytes of content.
Your program will implement a function, retrieve_url
, which takes a url (as
a str
) as its only argument, and uses the HTTP protocol to retrieve and
return the body’s bytes (do not decode those bytes into a string). Consult
the book or your class notes for the basics of the HTTP protocol.
You may assume that the URL will not include a fragment, query string, or
authentication credentials. You are not required to follow any redirects -
only return bytes when receiving a 200
response from the server. If for
any reason your program cannot retrieve the resource correctly or the
server returns a response that is not a valid HTTP response,
retrieve_url
should return None
.
As a rule of thumb, you should assume that you are building a client
that behaves the same as curl
when given the -f
flag
(curl -f <url>
). However, in any cases where
curl
and this specification differ, your client should behave as
described in this document. For example, in our testing, curl
returns
a body when fetching the URL
https://www.fieggen.com/shoelace,
even though the server returned a “301” Status-Code. In this case, your
client should return None
regardless of the behavior of curl
,
because this is the behavior described in this assignment specification.
In addition to this basic HTTP/1.1 client functionality, you are required to support HTTPS (SSL), and you are required to support connecting to hosts that only have IPv6 addresses in addition to also supporting IPv4 addresses.
Validating HTTPS
Within the retrieve_url
you will also be required to ensure that when
connecting to a HTTPS based website, your client ensures a valid
certificate is provided by the server. The ssl
library in Python will
do this for you by default. If the returned certificate is invalid
(expired, wrong host etc) and cannot be validated retrieve_url
returns
None
. To test your code in addition to the provided tests, there are
examples available at badssl.com.
DNS support for IPv4 and IPv6 Addresses
In order for your client to connect to the servers specified by hostname in the URL, your client must perform a DNS lookup. Recall from class that DNS can be used to map hostnames to either IPv4 or IPv6 addresses. As such, your client is required to support finding and connecting to both IPv4 and IPv6 addresses.
In python, this is done through the getaddrinfo
function of the
socket
module. Importantly, this function returns both IPv4 and IPv6
addresses found from the DNS lookup. Your client should then correctly
interpret the addresses it finds and try to connect to each one of these
addresses until at least one connection succeeds. In other words, if
your client is unable to connect to an IPv4 address but is able to
connect to an IPv6 address, then this is not an error and your client
should continue to issue a request and read the response. The same is
true if your client is not able to connect to an IPv6 address, but can
connect to an IPv4 address.
HTTP/1.1 Overview
The HTTP/1.1 RFC specifies the HTTP/1.1 protocol. Because you are expected to build a client that is strictly compliant with the HTTP/1.1 protocol, reading and understanding this RFC is important. The HTTP/1.1 protocol is very permissive. For this homework, your submission will be graded against simple custom HTTP servers that generate unusual but protocol compliant responses, and your program is expected to correctly process and accept these responses. Additionally, to demonstrate your understanding of the HTTP protocol and the correctness of your program, you are also required to create new HTTP responses that are both valid and invalid and test for edge cases in the HTTP protocol.
However, your HTTP client is not required to generate every type of HTTP request nor handle every type of HTTP response. Instead, you are only required to generate requests with the “GET” method, and you are only required to handle responses returned with the 200 status (“OK”).
To help you correctly implement the HTTP protocol, the rest of this section includes notes and tips on the aspects of the HTTP protocol that you are required to implement. However, you may also find it useful to read James Marshall’s excellent HTTP Made Really Easy under the HTTP/1.1 clients subsection.
Note that the RFCs are your friends: if you’re having trouble with
Transfer-encoding
, check the RFC for hints!
Text in HTTP
HTTP is a text-based protocol. Correctly handling the different ways that text can be formatted in the HTTP protocol is an important part of building a client that correctly implements the HTTP protocol, and there are a few subtleties of the HTTP protocol that are easy to get wrong:
-
CRLF: From Section 2.2 of the RFC, “HTTP/1.1 defines the sequence CR LF as the end-of-line marker for all protocol elements except the entity-body (see appendix 19.3 for tolerant applications). The end-of-line marker within an entity-body is defined by its associated media type, as described in section 3.7.”
CRLF = CR LF
- Case-Insensitive: From Section 2.1 of the RFC, “Unless stated otherwise, the text is case-insensitive.”
- Implied *LWS: From Section 2.1 of the RFC, “The grammar described by this specification is word-based. Except where noted otherwise, linear white space (LWS) can be included between any two adjacent words (token or quoted-string), and between adjacent words and separators, without changing the interpretation of a field. At least one delimiter (LWS and/or separators) MUST exist between any two tokens (for the definition of “token” below), since they would otherwise be interpreted as a single token.” -> This means that extra whitespace in headers is still a valid HTTP header.
-
Special characters and quoted strings: From Section 2.2 of the RFC, “Many HTTP/1.1 header field values consist of words separated by LWS or special characters. These special characters MUST be in a quoted string to be used within a parameter value (as defined in section 3.6).”
separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
and “A string of text is parsed as a single word if it is quoted using double-quote marks.”
quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) qdtext = <any TEXT except <">>
- Comments: From Section 2.2 of the RFC, “Comments can be included in some HTTP header fields by surrounding the comment text with parentheses. Comments are only allowed in fields containing “comment” as part of their field value definition. In all other fields, parentheses are considered part of the field value.”
HTTP GET Requests
GET is the only “Method” of HTTP request that your client is required to support. For reference, HTTP requests are specified in Section 5 of the RFC. From this section, we can see that a request is defined as follows:
Request = Request-Line ; Section 5.1
*(( general-header ; Section 4.5
| request-header ; Section 5.3
| entity-header ) CRLF) ; Section 7.1
CRLF
[ message-body ] ; Section 4.3
To generate a policy-compliant GET request, you will have to generate a
Request-Line, a Host:
header, and a Connection: close
header, and
merge them together with the appropriate CRLF newlines (\r\n
).
In more detail:
-
Request-Line: From Section 5.1 of the RFC: “The Request-Line begins with a method token, followed by the Request-URI and the protocol version, and ending with CRLF. The elements are separated by SP characters. No CR or LF is allowed except in the final CRLF sequence.”
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
- GET Method: Because you are only supporting “GET”, the method in your request-line should always be “GET” (Section 5.1.1).
-
absoluteURI: From Section 5.1.2, “The absoluteURI form is REQUIRED when the request is being made to a proxy. The proxy is requested to forward the request or service it from a valid cache, and return the response. Note that the proxy MAY forward the request on to another proxy or directly to the server specified by the absoluteURI. In order to avoid request loops, a proxy MUST be able to recognize all of its server names, including any aliases, local variations, and the numeric IP address. An example Request-Line would be:”
GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1
-
Host Header: From Section 14.23, “A client MUST include a Host header field in all HTTP/1.1 request messages.”, and “The Host request-header field specifies the Internet host and port number of the resource being requested, as obtained from the original URI given by the user or referring resource (generally an HTTP URL, as described in section 3.2.2). The Host field value MUST represent the naming authority of the origin server or gateway given by the original URL. This allows the origin server or gateway to differentiate between internally-ambiguous URLs, such as the root “/” URL of a server for multiple host names on a single IP address.”
Host = "Host" ":" host [ ":" port ] ; Section 3.2.2
- See sections 5.2 and 19.6.1.1 for other requirements relating to Host.
-
Connection: close: For simplicity, your client is not required to support persistent connections. However, From Section 19.6.2, “Persistent connections are the default for HTTP/1.1 messages; we introduce a new keyword (Connection: close) for declaring non-persistence. See section 14.10.”
-
From Section 14.10, “HTTP/1.1 defines the “close” connection option for the sender to signal that the connection will be closed after completion of the response. For example,
Connection: close
in either the request or the response header fields indicates that the connection SHOULD NOT be considered `persistent’ (section 8.1) after the current request/response is complete.
HTTP/1.1 applications that do not support persistent connections MUST include the “close” connection option in every message.”
-
HTTP Responses
Your client only needs to support responses that come with a status-code of “200” (OK). To do this, your client will need to be able to correctly parse and validate HTTP responses (Section 6). From the RFC, “After receiving and interpreting a request message, a server responds with an HTTP response message.”
Response = Status-Line ; Section 6.1
*(( general-header ; Section 4.5
| response-header ; Section 6.2
| entity-header ) CRLF) ; Section 7.1
CRLF
[ message-body ] ; Section 7.2
Most of the difficulty in building a client that is strictly compliant with the HTTP protocol is in correctly parsing and validating HTTP Responses. This is because, unlike requests, whose format is completely controlled by your code, responses are received over the network from a server that your client connects to, and different servers are allowed to format their responses differently by the HTTP protocol standard.
Their are two primary components to an HTTP response: headers and the message-body. The rest of this section describes these components in more detail.
HTTP Response Headers
HTTP response headers start with a Status-Line, are optionally followed
by some number of headers, and are terminated with a CRLF (\r\n
).
In more detail:
-
Status-Line: From Section 6.1, “The first line of a Response message is the Status-Line, consisting of the protocol version followed by a numeric status code and its associated textual phrase, with each element separated by SP characters. No CR or LF is allowed except in the final CRLF sequence.”
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
- Status-Code: Section 6.1.1 defines the different status codes
in HTTP. Your client must ignore all responses (return
None
) that do not have the “200” (OK) Status code. -
Reason-Phrase: From Section 6.1.1, “The Reason-Phrase is intended to give a short textual description of the Status-Code. The Status-Code is intended for use by automata and the Reason-Phrase is intended for the human user. The client is not required to examine or display the Reason- Phrase.”. Also, “The reason phrases listed here are only recommendations – they MAY be replaced by local equivalents without affecting the protocol.”
Reason-Phrase = *<TEXT, excluding CR, LF>
- Status-Code: Section 6.1.1 defines the different status codes
in HTTP. Your client must ignore all responses (return
-
Content-Length Header: From Section 14.13 of the RFC: “The Content-Length entity-header field indicates the size of the entity-body, in decimal number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of the entity-body that would have been sent had the request been a GET.
Content-Length = "Content-Length" ":" 1*DIGIT
An example is
Content-Length: 3495
Applications SHOULD use this field to indicate the transfer-length of the message-body, unless this is prohibited by the rules in section 4.4.
Any Content-Length greater than or equal to zero is a valid value. Section 4.4 describes how to determine the length of a message-body if a Content-Length is not given.”
-
Transfer-Encoding: chunked: From Section 4.4 of the RFC: “All HTTP/1.1 applications that receive entities MUST accept the “chunked” transfer-coding (section 3.6), thus allowing this mechanism to be used for messages when the message length cannot be determined in advance.” Thus, although your client is not required to support all different types of transfer-coding, it must support the “chunked” transfer-coding.
From Section 14.41, “14.41 Transfer-Encoding
The Transfer-Encoding general-header field indicates what (if any) type of transformation has been applied to the message body in order to safely transfer it between the sender and the recipient. This differs from the content-coding in that the transfer-coding is a property of the message, not of the entity.
Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding
Transfer-codings are defined in section 3.6. An example is:”
Transfer-Encoding: chunked
Also, from SEction 14.39, “ If the TE field-value is empty or if no TE field is present, the only transfer-coding is “chunked”. A message with no transfer-coding is always acceptable.”
-
Message Length: Your client should correctly handle servers that use a variety of different ways to indicate message length. This includes
Transfer-Encoding: chunked
, theContent-Length:
header field, and even by the server closing the connection.To provide some more details on how message length is determined in HTTP/1.1, from Section 4.4 of the RFC:
“2.If a Transfer-Encoding header field (section 14.41) is present and has any value other than “identity”, then the transfer-length is defined by use of the “chunked” transfer-coding (section 3.6), unless the message is terminated by closing the connection.
3.If a Content-Length header field (section 14.13) is present, its decimal value in OCTETs represents both the entity-length and the transfer-length. The Content-Length header field MUST NOT be sent if these two lengths are different (i.e., if a Transfer-Encoding header field is present). If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.
5.By the server closing the connection. (Closing the connection cannot be used to indicate the end of a request body, since that would leave no possibility for the server to send back a response.)”
-
Header field continuation/LWS: From Section 2.2 of the RFC, “A CRLF is allowed in the definition of TEXT only as part of a header field continuation.” Also from Section 2.2: “HTTP/1.1 header field values can be folded onto multiple lines if the continuation line begins with a space or horizontal tab. All linear white space, including folding, has the same semantics as SP. A recipient MAY replace any linear white space with a single SP before interpreting the field value or forwarding the message downstream.” Note: you should expect your program to be tested against responses with header field continuations.
LWS = [CRLF] 1*( SP | HT )
HTTP Response Bodies
All messages with a “200” Status-Code must include a message-body, although it may be of zero length (Section 4.3).
From Section 4.3 of the RFC, “The message-body (if any) of an HTTP message is used to carry the entity-body associated with the request or response. The message-body differs from the entity-body only when a transfer-coding has been applied, as indicated by the Transfer-Encoding header field (section 14.41).
message-body = entity-body
| <entity-body encoded as per Transfer-Encoding>
Transfer-Encoding MUST be used to indicate any transfer-codings applied by an application to ensure safe and proper transfer of the message. Transfer-Encoding is a property of the message, not of the entity, and thus MAY be added or removed by any application along the request/response chain. (However, section 3.6 places restrictions on when certain transfer-codings may be used.)”
Your client should support the “chunked” Transfer-Encoding. Additionally, your client should support a server using “Content-Length” and closing a connection to determine the end of a message body.
A few more details:
-
Content-Length: From Section 10.2.7 of the RFC, “If a Content-Length header field is present in the response, its value MUST match the actual number of OCTETs transmitted in the message-body.”
This implies that reading a response body given a content length is easy. You should repeatedly use the
recv
system call to read data from the socket until the correct number of OCTETs (bytes) have been read. -
Transfer-Encoding: chunked: From Section 3.6.1 of the RFC: “The chunked encoding modifies the body of a message in order to transfer it as a series of chunks, each with its own size indicator, followed by an OPTIONAL trailer containing entity-header fields. This allows dynamically produced content to be transferred along with the information necessary for the recipient to verify that it has received the full message.
Chunked-Body = *chunk last-chunk trailer CRLF chunk = chunk-size [ chunk-extension ] CRLF chunk-data CRLF chunk-size = 1*HEX last-chunk = 1*("0") [ chunk-extension ] CRLF chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) chunk-ext-name = token chunk-ext-val = token | quoted-string chunk-data = chunk-size(OCTET) trailer = *(entity-header CRLF)
The chunk-size field is a string of hex digits indicating the size of the chunk. The chunked encoding is ended by any chunk whose size is zero, followed by the trailer, which is terminated by an empty line.
The trailer allows the sender to include additional HTTP header fields at the end of the message. The Trailer header field can be used to indicate which header fields are included in a trailer (see section 14.40).”
-
Algorithm for decoding the chunked transfer-coding: From Section 19.4.6 of the RFC, “A process for decoding the “chunked” transfer-coding (section 3.6)
length := 0 read chunk-size, chunk-extension (if any) and CRLF while (chunk-size > 0) { read chunk-data and CRLF append chunk-data to entity-body length := length + chunk-size read chunk-size and CRLF } read entity-header while (entity-header not empty) { append entity-header to existing header fields read entity-header } Content-Length := length Remove "chunked" from Transfer-Encoding
-
; chunk-extension: Note that every line containing a chunk-size may also optionally have a
;
character followed by a chunk-extension.
-
Skeleton Code
A well-documented and commented template is provided in this repository,
as hw3.py
. This contains the necessary modules you need to complete
this homework. You are likely not allowed to import any additional
modules. Specifically, unless you are otherwise granted permission via
Piazza, you are not allowed to import any additional modules.
The provided skeleton code contains many mostly empty functions. You are not required to use all of these functions. In other words, you are allowed to create your own new functions and modify the control flow of the application. That said, you also do not have to create any new functions to implement a correct client. The provided functions, implemented correctly, are sufficient.
However, if you change the control flow of the provided skeleton application, you are required to explain why in your WRITEUP. Further, as always, you are required to document any outside sources that you consulted.
The rest of this section describes the different functions in the skeleton code in more detail:
-
parse_url
: This function should take in the URL and return a (scheme, hostname, port, path) tuple. To implement this function, you are encouraged to use theurlparse
function that is already imported for you. However, you will still have to perform error checking to validate the URL and to pick the default ports for HTTP and HTTPS URLs if a port is not specified. -
open_connection
: This function takes as input a hostname and port and returns an open socket. To implement this function so that it correctly handles DNS lookups for both IPv4 and IPv6 addresses, you will need to use thesocket.getaddrinfo()
,socket.socket()
, ands.connect()
functions. -
wrap_socket_ssl
: This function takes as input a socket, validates the server’s certificates and hostname, and returns a wrapped socket. All of the SSL functionality of your client can be contained in this one function. -
gen_http_req
: This function takes as input a hostname, port, and path and returns an encoded (correct) HTTP GET Request with the appropriate headers. Note that you will likely need to call thestr.encode()
function of a string to go from a Python string to bytes. You may also want to pass the input URL to this function as well. -
send_req
: Sends an HTTP GET request (bytes) on a socket. Returns True on success and False on error. To implement this function, you are welcome to use either thes.send()
ors.sendall()
functions. However, if you uses.send()
, you must ensure that the entire request is sent, even given that send can return after only sending a partial message. Further, both of these functions can raise exceptions, which should be caught. -
parse_headers
: Given the raw header bytes received from the socket, parse the headers. This includes separating the Status-Line, and this includes getting a list of field name, field value pairs. Note that correctly implementing this function requires support for variable amounts of white space, variable cases (case-insensitivity), and folded field values. If the headers are not correctly formatted so that they can be parsed, this is an error andNone
should be returned for the body. -
check_status_line
: Check if the status line is correct. This means that any valid status line should be accepted. Important things to check for in this function include case-insensitivity, support for white space, a status value of “200”, and tolerating the absence of the optional Reason-Phrase. -
validate_headers
: Validate all of the headers, including the Status-Line. Once again, this function is required to strictly enforce the HTTP protocol while also ensuring that it accepts all headers that are protocol-compliant. -
get_body_len_info
: Check the headers to determine how long the body will be. Note that if no “Content-Length” is specified, and the “Transfer-Encoding” field is not chunked, this is still not necessarily an error. -
get_body_content_len
: Get the body given a Content-Length header was found. Takes as arguments a socket s, any bytes of the body that have already been ready asbody_start
, and the length of the body from the Content-Length field value. This function should userecv
to get the response. Note thatrecv
is allowed to return less than the requested amount of bytes (including 0). As a result, you should callrecv
until you have received all of the bytes specified by the protocol. -
get_body_chunked
: Get the body givenTransfer-Encoding: chunked
. Takes as arguments a socket s and any bytes of the body that have already been ready asbody_start
. See Section 19.4.6 of the RFC for pseudo-code on how to decode chunked messages. -
read_resp
: Read an HTTP response from a socket s given that a request has already been written to the socket. Returns None if the response is not a valid HTTP response with a Status-Code of “200”, otherwise returns the body of the response. -
retrieve_url
: Given a URL, issue an HTTP GET Request to fetch the content at this URL. If any errors are encountered, or the response is not a valid HTTP Response, then None is returned. Otherwise, the body is returned as a bytestring (bytes).
Test Cases
As part of the grading for this assignment, you are required
to create new test cases that exercise your program and demonstrate your
understanding of the HTTP protocol and its corner cases. To help you
with this step, we are providing you with 1) test cases that compare
your program against the output of curl -f
and 2) simple example test
HTTP servers that you can use to test different response headers and
bodies against both your own program. The test harness is located in
tester.py
. The example test servers are located in
tests/test_server*.py
.
Your task for this assignment is to then follow the example test cases and create your own new tests. This includes two types of tests: 1) new URLs to test your program against, and 2) new simple static HTTP servers that demonstrate your knowledge of edge-cases in the HTTP protocol and ability to test your program against curl. Also, instead of requiring a set amount of student test cases, you are instead required to submit all of the test cases that you create and then explain in the documentation in each individual write-up what edge-cases these test against and why you believe that all of the tests you have created are sufficient for testing the correctness of your program.
Running Tests and Example Test Cases
Running the test cases is simple, at least in the Ubuntu environment
that the scripts were developed it. The entire test harness can be run
by typing: ./tester.py --hw3-path hw3.py
- Note: If there are any Windows and MacOS errors, they should be discussed on Piazza
If everything is setup correctly, running ./tester.py --hw3-path
hw3.py
should correctly run your program for all of the test cases that
you configured in ./tester.py
, which includes custom static python
HTTP servers that you have created in the ./tests/
folder. See the
individual files for more documentation, but, to give an overview, here
are some important notes:
- Add all of your custom HTTP servers to the list
TEST_SERVERS
intester.py
. Note that this functionality assumes that the test servers are executable files on your system. This is made possibly easily on Unix platforms with the shebang used in the examples, but this may not be as simple on Windows platforms. Note that you can either modify it or skip it entirely and just ensure by hand that your test servers are running before you run this test script if this is not working for you. - Add all of the URLs that you would like to compare against to the
TEST_CASES
list intester.py
. Note that this should include any local URLs and ports (e.g.,127.0.0.1:4500
) - In cases where this assignment specification and the output of the
script differ, this assignment specification is correct.
- Any case where you believe the output of this script is incorrect, for any reason, is an interesting test case. Note that all test cases that are sufficiently interesting are worth extra credit. This inludes errors in our test infrastructure, and this also includes suspected errors in applications like curl and Firefox.
There is a collection of example test HTTP servers in the ./test/
directory. Here are a few important notes:
- The
tests/test_server.py
application is intended to be the basis for all of your example HTTP test servers. You are encourage to copy and modify this file in any way you need. - Part of your grading is upon the documentation for the test cases that
you create, and the place to do so is inside the source file that you
create for each test server. The example servers in
tests/test_server0*.py
contain example test documentation that you should follow.
Submission and Grading
For this homework we will be using an autograder to grade your work, and
./tester.py
is the autograder that we will use. Like before, you can
create as many backups as you like using git commit -a
and git push
.
There is a total of 100 points on this assignment, with extra bonus points being awarded for exceptional new test cases. Additionally, there are extra credit points available for finding bugs in any part of this assignment infrastructure.
The points for this assignment are broken down as follows:
- 50 Points: Correctness of the student HTTP client
- 35 Points (5 cases @ 7 points each): New test cases (including documentation)
- 15 Points: Code style for both HTTP clients and test cases
Note that simply submitting 5 test cases is not sufficient to get full credit for the test cases. For example, if all of the same test cases test the same aspect of the HTTP, then you will not receive full credit because the test cases are not sufficiently dissimilar. In particular, you need to explain in your writeup why each test case is unique for full credit for this component of the homework.
Submission
You will be making the submission for this homework through github.
The files which will be submitted through github:
hw3.py
: This file will contain your version of theretrieve_url
To submit your work, run git commit -a
and git push
. Like previously, you are allowed to make multiple submissions
but you have limited attempts with the grader.
Due Date and Logistics
-
This assignment is due at the at 11:59 pm on Monday April 13.
-
If you have any questions please use the Piazza discussion forum.