Skip to content
Closed
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
54 changes: 54 additions & 0 deletions Pei_HW/session01/echo_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import socket
import sys


def client(msg, log_buffer=sys.stderr):
server_address = ('localhost', 10000)
# TODO: Replace the following line with your code which will instantiate
# a TCP socket with IPv4 Addressing, call the socket you make 'sock'
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
print('connecting to {0} port {1}'.format(*server_address), file=log_buffer)
# TODO: connect your socket to the server here.
sock.connect(server_address)
# you can use this as a place to accumulate the entire message echoed back
# from the server
received_message = b''

# this try/finally block exists purely to allow us to close the socket
# when we are finished with it
try:
print('sending "{0}"'.format(msg), file=log_buffer)
# TODO: send your message to the server here.
sock.sendall(msg.encode('utf8'))
# TODO: the server should be sending you back your message as a series
# of 16-byte chunks. Accumulate the chunks you get to build the
# entire reply from the server. Make sure that you have received
# the entire message and then you can break the loop.
#
# Log each chunk you receive. Use the print statement below to
# do it. This will help in debugging problems
done = False
while not done:
chunk = sock.recv(16)
if len(chunk) < 16:
done = True
received_message += chunk
print('received "{0}"'.format(chunk.decode('utf8')), file=log_buffer)
finally:
# TODO: after you break out of the loop receiving echoed chunks from
# the server you will want to close your client socket.
print('closing socket', file=log_buffer)
sock.close()
# TODO: when all is said and done, you should return the reply you got
# from the server as the value of this function.
return received_message.decode('utf8')


if __name__ == '__main__':
if len(sys.argv) != 2:
usage = '\nusage: python echo_client.py "this is my message"\n'
print(usage, file=sys.stderr)
sys.exit(1)

msg = sys.argv[1]
client(msg)
84 changes: 84 additions & 0 deletions Pei_HW/session01/echo_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import socket
import sys


def server(log_buffer=sys.stderr):
# set an address for our server
address = ('127.0.0.1', 10000)
# TODO: Replace the following line with your code which will instantiate
# a TCP socket with IPv4 Addressing, call the socket you make 'sock'
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
# TODO: You may find that if you repeatedly run the server script it fails,
# claiming that the port is already used. You can set an option on
# your socket that will fix this problem. We DID NOT talk about this
# in class. Find the correct option by reading the very end of the
# socket library documentation:
# http://docs.python.org/3/library/socket.html#example
#sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# log that we are building a server
print("making a server on {0}:{1}".format(*address), file=log_buffer)

# TODO: bind your new sock 'sock' to the address above and begin to listen
# for incoming connections
#sock.bind(address)
sock.bind(address)
sock.listen(1)

try:
# the outer loop controls the creation of new connection sockets. The
# server will handle each incoming connection one at a time.
while True:
print('waiting for a connection', file=log_buffer)

# TODO: make a new socket when a client connects, call it 'conn',
# at the same time you should be able to get the address of
# the client so we can report it below. Replace the
# following line with your code. It is only here to prevent
# syntax errors
conn, addr = sock.accept()
# addr = ('bar', 'baz')
try:
print('connection - {0}:{1}'.format(*addr), file=log_buffer)

# the inner loop will receive messages sent by the client in
# buffers. When a complete message has been received, the
# loop will exit
while True:
# TODO: receive 16 bytes of data from the client. Store
# the data you receive as 'data'. Replace the
# following line with your code. It's only here as
# a placeholder to prevent an error in string
# formatting
data = conn.recv(16)
print('received "{0}"'.format(data.decode('utf8')))
# TODO: Send the data you received back to the client, log
# the fact using the print statement here. It will help in
# debugging problems.
if data:
print('sent "{0}"'.format(data.decode('utf8')))
conn.sendall(data)
# TODO: Check here to see if the message you've received is
# complete. If it is, break out of this inner loop.
else:
break
finally:
# TODO: When the inner loop exits, this 'finally' clause will
# be hit. Use that opportunity to close the socket you
# created above when a client connected.
print(
'echo complete, client connection closed', file=log_buffer
)
conn.close()
except KeyboardInterrupt:
# TODO: Use the python KeyboardInterrupt exception as a signal to
# close the server socket and exit from the server function.
# Replace the call to `pass` below, which is only there to
# prevent syntax problems
#pass
sock.close()
print('quitting echo server', file=log_buffer)


if __name__ == '__main__':
server()
sys.exit(0)
21 changes: 21 additions & 0 deletions Pei_HW/session01/socket_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import socket


def get_constants(prefix):
return {getattr(socket, n): n for n in dir(socket) if n.startswith(prefix)}


families = get_constants('AF_')
types = get_constants('SOCK_')
protocols = get_constants('IPPROTO_')


def get_address_info(host, port):
for response in socket.getaddrinfo(host, port):
fam, typ, pro, nam, add = response
print('family: {}'.format(families[fam]))
print('type: {}'.format(types[typ]))
print('protocol: {}'.format(protocols[pro]))
print('canonical name: {}'.format(nam))
print('socket address: {}'.format(add))
print
53 changes: 53 additions & 0 deletions Pei_HW/session01/tasks.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
Session 4 Homework
==================

Required Tasks:
---------------

* Complete the code in ``echo_server.py`` to create a server that sends back
whatever messages it receives from a client

* Complete the code in ``echo_client.py`` to create a client function that
can send a message and receive a reply.

* Ensure that the tests in ``tests.py`` pass.

To run the tests:

* Open one terminal while in this folder and execute this command:

$ python echo_server.py

* Open a second terminal in this same folder and execute this command:

$ python tests.py




Optional Tasks:
---------------

Simple:

* Write a python function that lists the services provided by a given range of
ports.

* accept the lower and upper bounds as arguments
* provide sensible defaults
* Ensure that it only accepts valid port numbers (0-65535)

Challenging:

* The echo server as outlined will only process a connection from one client
at a time. If a second client were to attempt a connection, it would have to
wait until the first message was fully echoed before it could be dealt with.

Python provides a module called `select` that allows waiting for I/O events
in order to control flow. The `select.select` method can be used to allow
our echo server to handle more than one incoming connection in "parallel".

Read the documentation about the `select` module
(http://docs.python.org/2/library/select.html) and attempt to write a second
version of the echo server that can handle multiple client connections in
"parallel". You do not need to invoke threading of any kind to do this.
46 changes: 46 additions & 0 deletions Pei_HW/session01/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from echo_client import client
import socket
import unittest


class EchoTestCase(unittest.TestCase):
"""tests for the echo server and client"""

def send_message(self, message):
"""Attempt to send a message using the client

In case of a socket error, fail and report the problem
"""
try:
reply = client(message)
except socket.error as e:
if e.errno == 61:
msg = "Error: {0}, is the server running?"
self.fail(msg.format(e.strerror))
else:
self.fail("Unexpected Error: {0}".format(str(e)))
return reply

def test_short_message_echo(self):
"""test that a message short than 16 bytes echoes cleanly"""
expected = "short message"
actual = self.send_message(expected)
self.assertEqual(
expected,
actual,
"expected {0}, got {1}".format(expected, actual)
)

def test_long_message_echo(self):
"""test that a message longer than 16 bytes echoes in 16-byte chunks"""
expected = "Four score and seven years ago our fathers did stuff"
actual = self.send_message(expected)
self.assertEqual(
expected,
actual,
"expected {0}, got {1}".format(expected, actual)
)


if __name__ == '__main__':
unittest.main()
109 changes: 109 additions & 0 deletions Pei_HW/session02/homework/http_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import socket
import sys
import mimetypes
import pathlib


def response_ok(body=b"this is a pretty minimal response", mimetype=b"text/plain"):
"""returns a basic HTTP response"""
resp = []
resp.append(b"HTTP/1.1 200 OK")
resp.append(b"Content-Type: " + mimetype)
resp.append(b"")
resp.append(body)
return b"\r\n".join(resp)


def response_method_not_allowed():
"""returns a 405 Method Not Allowed response"""
resp = []
resp.append("HTTP/1.1 405 Method Not Allowed")
resp.append("")
return "\r\n".join(resp).encode('utf8')


def response_not_found():
"""returns a 404 Not Found"""
resp = []
resp.append("HTTP/1.1 404 Not Found")
resp.append("")
return "\r\n".join(resp).encode('utf8')


def parse_request(request):
first_line = request.split("\r\n", 1)[0]
# example: request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
method, uri, protocol = first_line.split()
if method != "GET":
raise NotImplementedError("We only accept GET")
return uri


def resolve_uri(uri):
#import pdb; pdb.set_trace()
relative_path = pathlib.Path(uri.strip('/'))
home_dir = pathlib.Path('webroot')
map_pathname = home_dir/relative_path
if map_pathname.is_dir():
l = [p for p in map_pathname.iterdir()]
names = [p.name for p in l]
content = "\r\n".join(names).encode('utf8')
mime_type = 'text/plain'.encode('utf8')
elif map_pathname.is_file():
content = map_pathname.read_bytes()
mime_type = mimetypes.guess_type(uri)[0].encode('utf8')
else:
raise NameError()
#response = response_not_found()

"""This method should return appropriate content and a mime type"""
return content, mime_type


def server(log_buffer=sys.stderr):
address = ('127.0.0.1', 10000)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
print("making a server on {0}:{1}".format(*address), file=log_buffer)
sock.bind(address)
sock.listen(1)

try:
while True:
print('waiting for a connection', file=log_buffer)
conn, addr = sock.accept() # blocking
try:
print('connection - {0}:{1}'.format(*addr), file=log_buffer)
request = ''
while True:
data = conn.recv(1024)
request += data.decode('utf8')
if len(data) < 1024:
break

try:
uri = parse_request(request)
except NotImplementedError:
response = response_method_not_allowed()
else:
#import pdb; pdb.set_trace()
try:
content, mime_type = resolve_uri(uri)
except NameError:
response = response_not_found()
else:
response = response_ok(content, mime_type)

print('sending response', file=log_buffer)
conn.sendall(response)
finally:
conn.close()

except KeyboardInterrupt:
sock.close()
return


if __name__ == '__main__':
server()
sys.exit(0)
Loading