Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Develop a REST server script named measurement_server.py. This script will respo

ID: 3817281 • Letter: D

Question

Develop a REST server script named measurement_server.py. This script will respond to requests for data from the measurement database by sending back data that is JSON serialized. The server should listen on any interface at a particular port number.

Here are the paths and the requests that they correspond to

Path------------------------------------------------------------------- Function

/area----------------------------------------------------------Get a list of all areas

/area/(d+)/location----------------------------------------Get all locations for the given area id

/location/(d+)/measurement----------------------------Get all the measurements for the given location id

/area/(d+)/category---------------------------------------Get all the categories to which the given area belongs

/area/(d+)/average_measurement--------------------Get the average measurement for the given area

/area/(d+)/number_locations---------------------------Get the number of locations in the given area

  

Notes: • In the first four request types, return a list of dictionaries, JSON encoded

• In the last two request types, return the numbers. JSON encoded

  

You may wish to follow the organization used in the sakila_rest example in class.

Overview

This assignment will use the measurements example database as a subject. You will be providing data from this database as a service using REST. The measurements example is described in the Measurements Example.pdf. But, please note! Use the file measures.sqlite rather than recreate the file yourself.

Test Setup

The project “assign05_test.zip” contains a short test. The file test2.py is the test script, the other files are support. Note: the testing files should not be in the same directory with the server. Link to all the file required: http://ksuweb.kennesaw.edu/~bsetzer/4720su16/nanoc/output/assignments/5/

Explanation / Answer

reader_writer.py

import re

"""
    This provides reading and writing of characters for sockets.
    This is already provided by the Python library, but it looked interesting
        to try working it out directly.
"""


class reader_writer:
    """
    Provides reading and writing character services.
    Coding and decoding is handled.
    Data is transmitted in packets so there should be no decoding anomalies.
    """

    char_chunk_size = 128
    byte_buffer_size = 16*char_chunk_size

    def __init__(self, socket, encoding='utf-8'):
        self.socket = socket
        self.in_char_buffer = ""
        #self.out_char_buffer = ""
        self.stream_done = False
        self.encoding = encoding

    def read(self):
        """
        read one character and return
        return "" if the stream is done
        :return: the character read
        """
        if len(self.in_char_buffer) == 0:
            self.fill_char_buffer()
            if self.stream_done:
                return ""
        if len(self.in_char_buffer) > 0:
            rtval = self.in_char_buffer[0]
            self.in_char_buffer = self.in_char_buffer[1:]
            return rtval
        else:
            return ""

    def unread(self, ch):
        self.in_char_buffer = ch + self.in_char_buffer

    def fill_char_buffer(self):
        """
        Get some more bytes for the data buffer
        """
        data_bytes = self.socket.recv(reader_writer.byte_buffer_size)
        #print("Server received data bytes %d" % len(data_bytes))
        if len(data_bytes) > 0:
            self.in_char_buffer += data_bytes.decode(self.encoding)
        else:
            self.stream_done = True

    def write(self, data):
        """
        write a string to the socket.
        """
        self.socket.sendall(data.encode(self.encoding))

        # i = 0
        # while i < len(data):
        #     data_bytes = data[i:i+reader_writer.char_chunk_size].encode(self.encoding
        #     self.socket.send(data_bytes)
        #     i += reader_writer.char_chunk_size
        #     #print(" block sent ")

    def next_word(self):
        """
            Get the next word from the input stream of the socket.
            :return: the next word, empty if the input is exhausted
        """
        c = self.read()
        while re.match(r's', c):
            c = self.read()
        if len(c) > 0:
            word = c
            c = self.read()
            while re.match(r'S', c):
                word += c
                c = self.read()
            return word
        else:
            return ''

    def close_read(self):
        self.socket.shutdown(0)

    def close_write(self):
        self.socket.shutdown(1)

    def encoded_length(self, message):
        return len(message.encode(self.encoding))


      
main.py

from socket import socket
import json

import re

import logging

def server_port():
    return 12345

if __name__ == "__main__":

    logging.basicConfig(filename='example.log', level=logging.INFO)

    # setting up a listener socket
    sock = socket()
    sock.bind(('', server_port()))
    sock.listen(0) # 0 backlog of connections
    ACCEPTABLE_REQUEST_TYPES = [re.compile('/area'),
                                re.compile('/area/(d+)/location'),
                                re.compile('/area/(d+)/category'),
                                re.compile('/area/(d+)/average_measurement'),
                                re.compile('/area/(d+)/number_locations'),
                                re.compile('/location/(d+)/measurement')]

    while True:
        (conn, address) = sock.accept()
        rw = reader_writer(conn)
        logging.info('Connection made: {}'.format(conn))
        first_line = read_line(rw)
        logging.info('Client request: {}'.format(first_line))
        response = ''

        if any(re.search(regex, first_line) for regex in ACCEPTABLE_REQUEST_TYPES):
            if re.search(ACCEPTABLE_REQUEST_TYPES[5], first_line):
                ml_id = re.split('/', first_line)
                response = db.do_command('SELECT * FROM measurement WHERE measurement_location=?', [ml_id[2]])
            elif re.search(ACCEPTABLE_REQUEST_TYPES[4], first_line):
                la_id = re.split('/', first_line)
                response = db.do_command('SELECT COUNT(*) AS Number_of_Locations FROM location WHERE location_area=?', [la_id[2]])
            elif re.search(ACCEPTABLE_REQUEST_TYPES[3], first_line):
                am_id = re.split('/', first_line)
                response = db.do_command('SELECT AVG(value) AS Average_Measurement FROM measurement WHERE measurement_location=?', [am_id[2]])
            elif re.search(ACCEPTABLE_REQUEST_TYPES[2], first_line):
                cat_id = re.split('/', first_line)
                response = db.do_command('SELECT name from category NATURAL JOIN category_area WHERE area_id=?', [cat_id[2]])
            elif re.search(ACCEPTABLE_REQUEST_TYPES[1], first_line):
                loc_id = re.split('/', first_line)
                response = db.do_command('SELECT name, altitude FROM location WHERE location_area=?', [loc_id[2]])
            elif re.search(ACCEPTABLE_REQUEST_TYPES[0], first_line):
                response = db.do_command('SELECT * FROM area')
            logging.info('Server response: {}'.format(response))
            send_binary_response(rw, json.dumps(response).encode())
        else:
            logging.info('Bad request: {}: sending failed response'.format(first_line))
            send_binary_response(rw, "You have requested an unsupported request".encode(), status=400, status_remark='Bad Request')

        conn.shutdown(1) ## shutdown the sending side
        conn.close()
        logging.info("Closed connection")

      
socket_util.py


def read_line(rw):
    """
    Reads and returns one line from rw

    Line endings could be or

    :param rw: reader_writer
    :return:
    """
    the_line = ""
    ch = rw.read()
    while ch != ' ' and ch != ' ':
        the_line += ch
        ch = rw.read()
    ch = rw.read() # is this /n?
    if ch != ' ' and ch != ' ':
        rw.unread(ch)
    return the_line

def send_binary_response(rw, data, content_type='text/plain', status =200, status_remark='OK'):
    """

    :param rw:     A reader_writer to use to send the response
    :param message:
    :param content_type:
    :param status:
    :param status_remark:
    :return:
    """
    # first line
    # content-type
    # content-length (bytes)
    # blank line
    # content
    rw.write("HTTP/1.1 {} {} ".format(status, status_remark))
    rw.write("Content-type: {} ".format(content_type))
    rw.write("Content-length: {} ".format(len(data)))
    rw.write(" ")
    rw.socket.sendall(data)