Application Architectures

Introduction

Look at real-world application architectures for:

  1. Server used to present read-only database on web.

  2. E-commerce server for web.

  3. Vault for storing encryption keys.

  4. Random exercises.

Real World Architecture Constraints

Application architectures constrained by non-technical considerations:

  • What implementor is familiar with.

  • The need to integrate application with other functioning applications.

  • The need to evolve application while retaining compatibility.

  • Human organizational boundaries. Conway's law.

  • The technical culture of the team or organization.

Web Technology Overview

  • HTTP protocol (v1.0, 1.1) used between a web browser (like Mozilla) and web server (like Apache).

  • HTTP implemented on top of TCP/IP.

  • HTTP is stateless. To retain state, usual solutions are:

    • URL rewriting.

    • Hidden parameters to user forms.

    • Cookies.

  • To get a static file simply GET Path where Path is relative to the server document-root.

Web Technology Overview Continued

  • CGI protocol is an obsolete protocol which allows a web server to run external programs:

    • Can run a program with parameters passed in URL. Example: GET /cgi-bin/program?param1=value1&param2=value2. The parameters are passed to the program via the environmental variable QUERY_STRING.

    • Can run a program with parameters passed in POST request. The parameters are passed to the program via name: value pairs on standard input.

  • Most web servers can be extended with dynamically loaded modules which are accessed using a web-server specific API.

24 x 7 and Web Constraints

  • Web sites must stay up 24 x 7 unattendended. If serious problems, must ask for help.

  • Reality is that programs fail; possibly because of outright bugs, but often due to impossible changes in environment.

  • A web application is distributed across many client browsers. Hence upgrading a web application is non-trivial (a program on the web server may be ugraded but it must still deal with inputs accepted by the previous version).

  • Normally, a web server is accessed by a person using a web browser. But that is not always the case (robots); server programs need to be designed to deal with such situations:

    • Client-side parameter validation (using Javascript) is not adequate by itself.

    • Can receive rapid-fire requests much faster than the maximum possible by a human.

Read-Only Database to Web Initial Specs

  • Database represented using flat files.

  • Each flat file represented a database table.

  • There were about 15 tables containing a total of about 200,000 records containing a total of about 5 MB of data.

  • There was a single parameterized query with about 4 parameters.

  • Should run on single machine.

  • No DBMS available.

  • Problem was presented as:

    Here is a C program which takes a query and produces the HTML. Unfortunately, it runs very slow (30 secs cpu time / query). Speed it up.

Design Decisions

  • Relatively small amount of data.

  • Suck data into memory and build indexes to read data in memory.

  • Code for indexing and accessing in-memory data generated automatically from table descriptions.

  • Since loading all data into memory takes a large amount of time, amortize the data load time over a large number of queries.

  • Using the loaded data over multiple queries requires a architecture where a server program containing the loaded data responds to CGI clients.

  • For reliability, use a concurrent server.

  • Copy-on-write key to preventing data copying on fork() of concurrent server.

  • Overall design ascii-art.

Detailed Protocol

  • When the server program is started up by an administrator (or bootup shell script), it loads all the database files. Then it spawns a child to be the daemon, so that the process initially started can terminate. It is the child which really becomes the server daemon. The daemon then listens on a well-known pipe REQUESTS for requests from CGI processes.

  • When a CGI process is started by a web server, it first creates a new named pipe for the server's response, using its process identifier PID to name the pipe. It then sends a request down the well-known REQUESTS pipe; the request includes its PID, along with the query parameters. It then opens the pipe it previously created for reading; the open blocks until another process (the child spawned by the server daemon) opens it for writing. It then substitutes the name=value pairs it reads on this response pipe into a HTML template which it writes onto its stdout (from where the web-server transfers it to the web page).

Detailed Protocol Continued

  • When the server daemon sees a request on the REQUESTS pipe, it immediately spawns a child (using a double-fork) to process the query and returns to listening at the REQUESTS pipe. The child parses the request, extracting the query parameters and the PID of the client. It uses the extracted PID to open the response pipe write-only. It then processes the query using the database it inherited from its parent after the fork. The query processing builds up the answer in an in-memory datastructure. Finally, it outputs the answer onto the response pipe as name=value pairs and terminates.

Performance

  • 10-20 msec/query.

  • 7-10 seconds for loading database.

  • 10-20 MB memory footprint.

Updating Data while Running

  • Need to periodically update data.

  • Data needs to be updated without losing user requests.

  • Server uploads data on receipt of SIGHUP signal.

  • SIGHUP handler writes a special pseudo request to REQUESTS FIFO with PID -1.

  • No requests processed while loading occurs. However, the requests are not lost but merely queued in the FIFO.

Updating Server Code Without Losing Requests

  • Server uses PID file to detect that another server is running. Normally, if it detects that situation it bales out.

  • If started with a special option, then it waits to get an exclusive lock on PID file.

  • After newly started server is waiting, original server is killed. New server takes over.

  • Complications because server executable may be demand-paged. Solution:

    1. Start new server with wait option using temporary location for server executable.

    2. Kill original running server. New server from temporary location takes over.

    3. Copy new server code to normal server executable location. Start it with wait option.

    4. Kill server running from temporary location. New server from normal server executable location takes over.

Requirements for E-Commerce Server for Web

  • Persistent shopping cart.

  • User authentication.

  • Credit card verification.

  • Interface to legacy enterprise system.

  • Necessary to evolve it into existing web site.

  • Java used for server implementation.

  • Server architecture ascii art

E-Commerce Server Highlights

  • Users authenticated using email address and password.

  • Password stored in mysql database using a 1-way hash function.

  • Credit cards encrypted using a symmetric encryption algorithm.

  • Persistent sessions implemented using SessionID cookie in browser.

  • Authentication implemented using AuthID cookie in browser. For security, AuthID cookies transmitted only over encrypted HTTPS connection.

  • To prevent session capture, SessionID and AuthID cookies consist of two parts: a unique part (generated using a simple counter) and a random part (generated using a random number generator).

  • Having asynchronous connection to legacy enterprise system permits 24x7 operation of web site even though legacy system is not 24x7.

Design Overview

  • Each request is handled by a separate thread in Java server.

  • Each request thread does a exclusive lock on the SessionID. Prevents data interference.

  • Java server also has daemon threads for tasks like sending out email and logging.

  • Uses agressive cacheing to prevent DBMS delays. When a user connects, most of that user's information is pulled into in-memory cache (until it times out due to inactivity).

  • The cache is write-thru. That is, all data updates are written back to database.

  • To provide quick response to the user, the response is sent to the CGI program before database write-backs.

  • Because of SessionID lock, a new request for same user cannot proceed until previous request has completed database write-back.

Java Complications

  • Java is OS-agnostic. Does not know anything about processes, daemons, FIFOs, etc.

  • Forced choice of TCP/IP as communication mechanism with the CGI program.

  • Needed a Perl wrapper program to run Java server as a daemon.

  • Stopping/reinitializing the server is clumsy:

    • Periodically, the server checks for the existence of a stop-file. Stops/reinitializes based on the existence/size of this stop-file.

    • A minimal web server was run within the Java server using a separate thread. The server can then be controlled using any web browser (over a authenticated connection).

Vault for Storing Secrets

  • Provide encryption/decryption services with secure storage for encryption keys.

  • Allowed different roles for humans.

  • No single human could individually get access to encryption keys.

  • Multiple servers for reliability.

  • Met Payment Card Industry (PCI) standards.

User Roles

Technical user

Allowed to stop/start vault servers and set up cipher administrators.

Cipher Administrator

Multiple cipher administrators for two groups A and B. Only a cipher administrator for a group can set up a cipher user for that group.

Cipher User

Multiple cipher users for each group A and B. In addition, to user-id and password, each cipher user also needed a keyphrase which was used for decrypting master key.

Vault Primordial State

Vault initialized in primordial state:

  1. A technical user was allowed to access the vault and set up a login and password.

  2. Technical user set up cipher administrators (login, initial password) for groups A and B.

  3. After changing their passwords, cipher administrators for a particular group set up cipher users (login, initial password) for that group.

  4. After changing their passwords, cipher users entered in their secret passphrase.

  5. Vault generated a master key. Low half of master key was encrypted using secret passphrase of cipher users from group A; high half of master key was encrypted using secret passphrase of cipher users from group B.

Vault ready for operation.

Encryption Services

  • Technical user would register a cipher service. During registration, technical user would specify key rotation lifetime as well as IP addresses allowed to access service.

  • Vault would generate a cipher key to be used for each rotation. This key would be encrypted using the vault master key.

  • Vault would service calls to cipher service:

    Encrypt

    Return encryption of some sensitive data using cipher key. Returned value would contain id of cipher key in current rotation.

    Decrypt

    Given previous encryption of some sensitive data, vault would return sensitive data.

  • Needed to keep around rotated keys as long as those key ids were stored externally.

Normal Vault Startup

When vault servers started up, they cannot provide any cipher services.

  1. A cipher user from group A must login and type in their secret passphrase. This would be used to decrypt the low half of the vault master key.

  2. A cipher user from group B must login and type in their secret passphrase. This would be used to decrypt the high half of the vault master key.

  3. Vault would rebuild the master key.

Server Setup

  • Multiple vault servers.

  • One server was designated the master. Only the master could be used for creating cipher services or administering users.

  • All servers could be used for cipher services using a client library over HTTPS.

  • If a particular server was down, client library automatically failed over to some other server.

  • When a server was started up, it checked for existence of any peer server. If one existed, it was used to get hold of the master key.

  • By keeping at least one server up, it was possible to update the vault without requiring cipher users to provide their secret passphrase.

  • Due to the very rare need to type in their passphrase, some cipher users forgot their passphrases/passwords. Fortunately not disastrous as there were muliple cipher users for each group.

Random Exercises

  • Each exercise corresponds to a program. Example: Set Operations, source code.

  • Exercises based on a Question class.

  • ChoiceQuestion subclass for multiple choice questions.

  • Exercise instance based on random parameters.

  • Exercise delivered to web page without any form controls.

  • JavaScript running within browser inserts form controls.

Exercise URLs

  • Each exercise has its own URL. Accessing that URL creates a new instance of that exercise having its own values for random parameters.

  • Submitting an exercise results in answer displayed using a different URL.

  • Answer URL contains a query parameter params which is a JSON stringification of all the random parameters.

  • Putting the random parameters in the URL makes it possible to bookmark answer URLs.

Exercise Parameters

Exercise parameters specified via parameter functions set up to return random results. For Set Operations:

    const PARAMS = [
      { choiceKeys:
          () => Rand.choices(N_CHOICES, SET_OPS_KEYS),
      },
      { questionKey:
          ({choiceKeys}) => Rand.choice(choiceKeys),
      },
      { _type: 'nonRandom',
        questionName:
	  ({questionKey}) => SET_OPS[questionKey].name,
        questionDesc:
	  ({questionKey}) => SET_OPS[questionKey].desc,
        answerIndex:
	  ({choiceKeys, questionKey}) =>
	    choiceKeys.indexOf(questionKey),
      },
    ];

Exercise Structure

class Exercise extends ChoiceQuestion {

  constructor(params) {
    super(params);
    this.addParamSpec(PARAMS);
    for (let i = 0; i < 5; i++) {
      this.choice(({vals, alts}) => choice(vals, alts[i]) + '.');
    }
    this.freeze();
    this.addQuestion(question(this.params));
    this.addExplain(explain(this.params, this.choiceOrder()));
    this.makeContent();
  }
  
}

Exercise Structure Continued

module.exports = Exercise;

Object.assign(Exercise, {
  id: 'exercise',
  title: 'Example Exercise',
});


function question(params) { ... }

function explain(params) { ... }