• Stars
    star
    626
  • Rank 71,755 (Top 2 %)
  • Language
    Go
  • Created over 10 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Polyglot is a distributed web framework that allows programmers to create web applications in multiple programming languages

Polyglot

Polyglot is experimental and incomplete at the moment. Please use with caution and at your own risk!

Polyglot allows programmers to collaborate and develop a single web app using multiple programming languages, libraries, environments and even different versions of the same language.

What does this mean?

It means no more programming language wars -- programmers can use the best language for the job and/or their favorite language to write different parts of the same web app.

It can also mean no dependency hell. Different parts of the same web app can use different libraries. So if you have a new feature that requires using a newer version of the same library you used before, you can just go ahead and use it.

This also means you can scale and evolve your web app, building on what you have built before, and taking down only those parts that you need to replace, and even replace your web app in a less destructive way. No more 'big bang' approach to upgrading your web platform!

A complex web framework

So what's the catch?

Polyglot increases the complexity in the effort to develop web apps. Unlike frameworks like Rails or Django or Express, Polyglot doesn't exist to make life easier for the programmer. In fact it adds the complexity of the application, not only in the deployment but also in the development.

As a programmer you trade complexity and effort for something you think is more important for the web app you're creating. In Polyglot, we are trading complexity and effort for:

  1. Performance scalability -- Polyglot responders are distributed and independent processes that can reside anywhere on a connected network
  2. Extensibility -- by creating an acceptor as a controller in an existing web app, you can extend the applications through Polyglot
  3. Multi-lingual, independent development -- Polyglot responders can be developed independently in different programming languages, libraries and environments

Polyglot is not for all web apps. You should only use Polyglot for web apps that need to be scaled in a highly performant way and/or need to be incrementally developed in multiple programming languages. For example, if your web app never needs to scale beyond a single server, you're probably better off using some other single language framework. And if once your web app is created, you or anyone else never need to add new features, Polyglot is probably not suitable either.

Why would you want to develop a web app in multiple programming languages? There are good, practical reasons:

  1. Web apps you write are systems and they change over time and can be written or maintained by different groups of people. If you're not restricted to a particular platform or language, then the chances of getting an incrementally better piece of software is higher.
  2. You can switch out the poor-performing responders and replace them with higher-performing ones. Different responders can have different criteria for performance, ease-of-development, ease-of-maintenance or quick turnaround in development. With a single programming language you are often forced to accept a compromise. With multiple programming languages, you can choose the platform and language as what you need for that particular responder
  3. Different responders can be written for specific performance gains or maintainability

Why is even a 'web framework'? Web frameworks make life easier for programmers by making it easier to create web apps. While the added complexity of writing web apps with Polyglot make things more difficult initially, over time, the application is easier to extend and to evolve. With a bigger picture in mind, Polyglot does make life easier for programmers.

Architecture

Polyglot has a very simple and basic architecture. It consists of 3 main components:

  1. Acceptor - the acceptor is a HTTP interface that takes in a HTTP request and provides a HTTP response. The acceptor takes in a HTTP request converts it into messages and sends it to the Polyglot Broker. Then depending on what is asked, it will return the appropriate HTTP response. The default implementation of the acceptor is in Go. To extend an existing web app, you can also optionally implement the acceptor as a controller in that web app.
  2. Broker - the broker is an intermediary that receives the messages that represent the HTTP request and forwards it to the corresponding responder.
  3. Responder - responders are standalone processes written in any language that receives messages from the broker and responds to it accordingly. In most web frameworks this is usually called the controller. However unlike most web framework controllers, the responders are actual independent processes that can potentially sit in any connected remote server. Responders contain the "business logic" of your code.

Architecture

Essentially, the Polyglot architecture revolves around using a broker to disassociate the processing units (responders) from the communications unit (acceptor), allowing the responders to be created in multiple languages.

###Flow

  1. Client sents a HTTP request to the acceptor
  2. The acceptor converts the request into JSON and sends it to the broker, and waits for a response
  3. The broker routes the message to the correct responder
  4. The responder processes the request message
  5. The responder sends the response back to the broker. The response message is an array that has 4 elements -- the route ID, the HTTP status code, a map of headers, and a body.
  6. The broker receives the response and uses the route ID to respond to the correct acceptor
  7. The acceptor uses the HTTP status code, response headers and the body, creates a HTTP response and sends it to the client

Acceptor

The acceptor is a communications unit that interacts with the external world (normally a browser but can also be another server calling an API). The default implementation is written in Go. The acceptor is sessionless and its main task is to accept requests and sends them to the broker, then receives the response and reply to the requestor. The communications with the broker is through ZMQ.

You can also extend an existing application by creating a controller in that application as an acceptor. There can be one or more acceptors in a single Polyglot application, for load-balancing purposes.

Broker

The broker is a load-balancing broker implemented in Go and communicates with both the acceptors and the responders through ZMQ. The broker queues responders that are registered with it and load balances amongst them using round-robin.

Responder

Responders are processing units that can be written in any programming language that can communicate with the broker through ZMQ. Responders are normally written as standalone processes. All responders essentially do the same thing, which is to process incoming requests and returns a message to the broker. Each responder must have a route ID, which is in the format of:

<HTTP method>/_/<URL path>

For example GET/_/hello/world is a route ID.

Each responder must also have a unique ID that identifies itself across all routes. It's the developer's responsibility to ensure the uniqueness of the ID as the broker will not check its uniqueness.

Installation and setup

My development environment in OS X Mavericks but it should work fine with most *nix-based environments.

First, install ZeroMQ 4.0.4.

Next, clone this repository in to a Go workspace. The default acceptor is written in Go and you'll need to build it.

Acceptor

The default Polyglot acceptor is written in Go. To install, just run:

go build

This should create a program called polyglot. To run the default acceptor:

./polyglot

To configure the acceptor, modify the config.json file and restart the acceptor. The acceptor also creates a log file called acceptor.log that shows the acceptor's activities.

Broker

The Polyglot broker is written in Go. To install, go to the broker directory and run:

go build

This should create a program called broker. To run the broker:

./broker

The broker creates a log file called broker.log that shows the broker's activities. There is a command line admin tool called polyadm in the polyadm directory.

Responder

The set of example responders are in the responders directory. To run them, you can either start them individually or you can use Foreman like I do.

To start the responders individually, go to the respective directories for eg the ruby directory and do this:

ruby responder.rb

This will start up the hello Ruby responder. Note that you have only started 1 responder. To start up and manage a bunch of responders, open up Procfile in the responders directory.

hello_ruby: ruby -C ./ruby responder.rb
hello_go: ./goresp/goresp

You will notice that the file consists of lines of configuration that starts up the responders. Now open up the file .foreman.

concurrency: 
  hello_ruby=5,      
  hello_go=5

As you can see, each line after concurrency: is a responder. The number configuration is the number of responders you want to start up. In this case, I'm starting up 2 hello_rubyand hello_go responders each.

To start all the responders at once:

foreman start

If you want to run this in the background without being cut off when you log out:

nohup foreman start &

Foreman is used for development and testing purposes only. If you want to run this in production, use Foreman to export out the configuration files in Upstart or launchd etc. Foreman should not be used in production.

Writing responders

Writing responders are quite easy. There are basically only a few steps to follow:

  1. Connect to broker using whichever ZMQ library the language has
  2. Set the identity of the responder (remember that it must be unique across all responders)
  3. Establish the route ID. This is basically the HTTP method followed by /_/ and then the route path. For example, if you want to set up a responder for a GET request going to the route hello then set up the route ID to be GET/_/hello
  4. Send the route ID to the broker to register the responder
  5. In a loop, first receive the request and process it
  6. Then respond to the broker with an array of 4 elements, each element must be a String:
  7. The route ID
  8. The HTTP response status. For example, if everything is fine, this is 200, if you want to redirect, this will be 302, if it's an error it should be a 5xx
  9. A hash/map/dictionary of headers. You should try to put in at least the 'Content-Type' header
  10. The HTTP response body. This must be a string

The examples below shows how this can be done in various languages. The full list of responders are in the responders directory, including sample (Hello World type) responders for:

  • C
  • Ruby
  • Go
  • Python
  • Java
  • Node.js

Please send pull requests for sample responders in other languages!

C

This example uses the high-level C library CZMQ. To build the responder, please build the CZMQ library first then run:

make

This will create the file responder which you can run from command line as a binary.

#include "czmq.h"
#include <uuid/uuid.h>
#define ROUTEID "GET/_/hello/c"

int main (void) {
    zctx_t *ctx = zctx_new ();
    void *responder = zsocket_new (ctx, ZMQ_REQ);

    char identity [37];
    uuid_t uuid;
    uuid_generate(uuid);
    uuid_unparse_lower(uuid, identity);
    
    zmq_setsockopt (responder, ZMQ_IDENTITY, identity, strlen (identity));
    zsocket_connect (responder, "tcp://localhost:4321");

    printf ("%s - %s responder ready\n", ROUTEID, identity);
    zstr_send(responder, ROUTEID);
    while (true) {
        char *msg = zstr_recv (responder);
        if (!msg) {
          printf ("No message received from broker");
          break;
        }        
        zstr_sendm (responder, ROUTEID);
        zstr_sendm (responder, "200");
        zstr_sendm (responder, "{\"Content-Type\": \"text/html\"}");
        zstr_send (responder, msg);
        zstr_free (&msg);
    }
    zctx_destroy (&ctx);
    return 0;
}

Ruby

This example uses the ffi-rzmq gem and returns a "Hello World" back to the browser.

require 'securerandom'
require 'bundler'
Bundler.require

broker = "tcp://localhost:4321"
routeid = "GET/_/hello/ruby"
identity = SecureRandom.uuid

puts "#{routeid} - #{identity} responder ready"

ctx = ZMQ::Context.new
client = ctx.socket ZMQ::REQ
client.identity = identity
client.connect broker

client.send_string routeid
loop do
  request = String.new
  client.recv_string request
  response = [routeid, "200", "{\"Content-Type\": \"text/html\"}", "Hello World"]
  client.send_strings response
end

Go

This example uses Go ZMQ4 bindings and returns the request (in JSON) to the browser.

package main

import (
	zmq "github.com/pebbe/zmq4"
	"fmt"
  "code.google.com/p/go-uuid/uuid"
)

const (
	ROUTEID = "GET/_/hello/go"
)

func main() {
	responder, _ := zmq.NewSocket(zmq.REQ)
	defer responder.Close()

	identity := uuid.New()
	responder.SetIdentity(identity)
	responder.Connect("tcp://localhost:4321")

	fmt.Printf("%s - %s responder ready\n", ROUTEID, identity)
	responder.Send(ROUTEID, 0)

	for {
		msg, err := responder.RecvMessage(0)
		if err != nil {
      fmt.Println("Error in receiving message:", err)
			break //  Interrupted
		}
    resp := []string{"200", "{\"Content-Type\": \"text/html\"}", msg[0],}
    fmt.Println("Responding with:", resp)
		responder.SendMessage(ROUTEID, resp)
	}
}

Python

This example uses PyZMQ and returns "Hello World" to the browser.

import zmq
import uuid

identity = str(uuid.uuid4())
routeid = "GET/_/hello/python"

context = zmq.Context(1)
responder = context.socket(zmq.REQ)
responder.setsockopt(zmq.IDENTITY, identity)
responder.connect("tcp://localhost:4321")

print "%s - %s responder ready" % (routeid, identity)

responder.send(routeid)

while True:
  request = responder.recv()
  if not request:
    break
  response = [routeid, "200", "{\"Content-Type\": \"text/html\"}", "Hello World"]
  responder.send_multipart(response)

Java

This example returns the request to the browser. Use the compile file to compile the java class.

import org.zeromq.ZMQ;
import java.util.UUID;

 
public class Hello {

  public static void main(String[] args) {
    ZMQ.Context context = ZMQ.context(1);
    String routeid = "GET/_/hello/java";
    String identity = UUID.randomUUID().toString();
    
    ZMQ.Socket socket = context.socket(ZMQ.REQ);
    socket.setIdentity(identity.getBytes());
    socket.connect ("tcp://localhost:4321");
 
    System.out.printf("%s - %s responder ready\n", routeid, identity);
    
    socket.send(routeid, 0);
    try {
      while (true) {
        String request = socket.recvStr();        
        
        socket.send(routeid, ZMQ.SNDMORE);
        socket.send("200", ZMQ.SNDMORE);
        socket.send("{\"Content-Type\": \"text/html\"}", ZMQ.SNDMORE);
        socket.send(request);
      }      
    } catch (Exception e) {
      socket.close();
      context.term();      
    } 
  }
}

Node.js

This example uses the node.js bindings to ZeroMQ. Install the 2 modules node-uuid and zmq before running this code.

var zmq = require('zmq')
  , sock = zmq.socket('req');
var uuid = require('node-uuid');

routeid = "GET/_/hello/node";
identity = uuid.v4();
sock.identity = identity;
sock.connect('tcp://localhost:4321');
sock.send(routeid);

console.log('%s - %s responder ready', routeid, identity);

sock.on('message', function(msg){  
  sock.send(routeid, zmq.ZMQ_SNDMORE);
  sock.send("200", zmq.ZMQ_SNDMORE);
  sock.send("{\"Content-Type\": \"text/html\"}", zmq.ZMQ_SNDMORE);
  sock.send(msg);  
});

Static files

To serve out static files, configure Polyglot to point to a directory you wish to serve the files from. For eg if you want to serve your files from the directory public you can change the settings in config.json like this:

{
  "Acceptor"       : "0.0.0.0:8080",
  "ReadTimeout"    : 10,
  "WriteTimeout"   : 600,
  "RequestTimeout" : 2500,
  "RequestRetries" : 3,
  "Broker"         : "tcp://localhost:1234",
  "Static"         : "public"
}

All files (including files in the various subdirectories) can now be served from http://<host>:8080/_static/ eg if you have a file public/css/main.css then you can access the file through http://<host>:8080/_static/css/main.css

Sample web app

I wrote a sample web app called Polyblog that puts together a simple blog app using Polyglot.

Extending an existing application

You can extend an existing web app by creating a controller in your application that emulates whatever the acceptor does (which is essentially to pack the HTTP request into JSON and send it to the broker, then wait for a response and pass it back to the calling client).

Command line admin

The broker has a command line administration tool named polyadm, which is found in the polyadmn directory. To build it run:

go build

Then run it like this:

./polyadm

There is limited functionality for the command line admin at the moment.

Current limitations

Polyglot is currently pretty limited. While it can do all of the above, some normally expected capabilities of a web framework are not easily achieved (yet), including (but not limited to) session management and authentication.

Credits and feedback

The idea of separating the request acceptor and the workload has been around for a while, in particular the enterprise world has been doing Service Oriented Architecture for a while, as with Message-oriented middleware(MOM). Task queues where you have clients and workers is also a common pattern used in many systems. The idea of returning an array of status, headers and body was inspired by WSGI.

There is also feedback that Polyglot is similar to Mongrel2. I'm not familiar with Mongrel2, and a preliminary reading tells me that it sounds like fantastic software.

More Repositories

1

gwp

Go Web Programming code repository
JavaScript
1,621
star
2

invaders

Space Invaders in Go
Go
638
star
3

gonn

Building a simple neural network in Go
Go
334
star
4

invadersapp

Space invaders in an app
Go
197
star
5

everyday

code from the book "Exploring Everyday Things with R and Ruby"
Ruby
170
star
6

muse

A Ruby DSL for making music
Ruby
122
star
7

saushengine.v1

Simple Ruby-based search engine
Ruby
118
star
8

ga

Simple genetic algorithms in Go
Go
113
star
9

snip

Simple TinyURL clone
Ruby
98
star
10

chirp

Simple Sinatra-based micro-blog/Twitter clone
Ruby
90
star
11

naive-bayes

Simple naive bayesian classifier implemented in Ruby
Ruby
64
star
12

tanuki

Tanuki is a polyglot web framework that allows you to develop web applications and services in multiple programming languages.
Go
62
star
13

mosaic

A photo-mosaic generating program, also showcasing Go concurrency techniques using goroutines and channels.
Go
60
star
14

persona

Talking head video AI generator
Python
56
star
15

talkie

A voice-based ChatGPT clone that can search on the Internet and also in local files
CSS
54
star
16

hs1xxplug

Go library for TP-Link HS100 and HS110 WiFi smart plug
Go
48
star
17

kancil

Simple web app to showcase LlamaIndex
Python
42
star
18

polyblog

How to Create A Web App In 3 Different Programming Languages
Java
41
star
19

chitchat

Simple forum written in Go
Go
37
star
20

breeze

A simple ChatGPT clone built using Go
HTML
31
star
21

goids

Flocking simulation in Go
Go
29
star
22

goreplicate

This is a simple Go package for interacting with the Replicate (https://replicate.com) HTTP APIs. Replicate is an API service that allows developers to use machine learning models easily through calling APIs.
Go
27
star
23

blueblue

Bluetooth LE scanner and spelunking tool
Go
25
star
24

exblog

A blogging web application written with Elixir and Dynamo.
Elixir
21
star
25

modular

Examples uses to illustrate how to write modular Rack-based web applications
Ruby
21
star
26

tinyclone

TinyURL clone
Ruby
20
star
27

carpark-cgpt

ChatGPT plugin for Singapore HDB car park availability
Go
17
star
28

ruby-gpio

A Ruby DSL to interface with the Raspberry Pi GPIO.
Ruby
17
star
29

saushengine

Ruby
16
star
30

tanks

Tanks! is a Gosu-based simple, real-time online multiplayer game, based on the popular retro game, Tank Battalion.
Ruby
14
star
31

Colony

Facebook clone project for the Cloning Internet Applications with Ruby book
JavaScript
14
star
32

netnet

Discover Wi-Fi clients using Raspberry Pi Zero W, airodump-ng and Go
Go
13
star
33

rbase

A minimalist NoSQL database written in pure Ruby.
Ruby
13
star
34

petri

Go framework for building simulations based on cellular automation
Go
13
star
35

openai

Go package that wraps around OpenAI HTTP APIs
Go
12
star
36

gocookbook

Code repository for the Go Cookbook
Go
12
star
37

monsoon

Monsoon is a simple ChatGPT clone built with Go. It uses Llama-compatible LLMs, through llama.cpp.
CSS
12
star
38

ghost

"What if a cyber brain could possibly generate its own ghost, create a soul all by itself?"
Python
11
star
39

utopia

Pure Ruby version of 'Money, Sex and Evolution' agent-based modeling simulations
Ruby
11
star
40

gonb

Go
11
star
41

Wavform

Generate MP3 waveforms using Ruby and R
Ruby
11
star
42

epidemic-sim

Epidemic simulation using Go and Python
Jupyter Notebook
10
star
43

auth

Examples of third-party authentication, using Sinatra and Shoes, with RPX, OpenID etc
Ruby
10
star
44

easyblog

A minimalist blog web app
Ruby
9
star
45

snip-appengine

Snip! deployment on Google AppEngine
Ruby
9
star
46

squall

Squall is a Question Answering chatbot that runs entirely on a laptop, using Llama-2 and a downloaded HuggingFace embedding model.
Python
9
star
47

vdb

Sample code to show how to create an in-memory RAG
Go
9
star
48

pynn

Building simple artificial neural networks with TensorFlow, Keras, PyTorch and MXNet/Gluon
Python
8
star
49

gost

Gost is a native Go data store for storing data in S3 compatible object storage services.
Go
8
star
50

gwp2

Code for Go Web Programming 2nd edition
Go
8
star
51

maiad

My AI Assistant for Microsoft Word
HTML
7
star
52

merkato

E-Commerce application with Go
Go
7
star
53

sghazeserv

Singapore Haze Watch server
Go
7
star
54

ruby_complexity_simulations

Source code for Programming Complexity (Ruby version) talk
JavaScript
7
star
55

founders

Algorithms for Startup Founders
Jupyter Notebook
7
star
56

promptscript

PromptScript is an experimental prompting language created by GPT-4
Python
7
star
57

gomuse

Creating music with Go
JavaScript
7
star
58

house

House is a debate simulation between multiple participants, which can be represented by different large language models (LLMs). House is an experiment to use LLMs to debate and discuss a topic and get views from multiple perspectives.
Python
7
star
59

chirpy

Simple Twitter clone
Ruby
6
star
60

easyforum

A minimalist forum web application for Rubyists
Ruby
6
star
61

tanksworld

Web-based Tanks game server management
Ruby
6
star
62

gotext

Random Text Generator written in Go
Go
6
star
63

anthill

Simple workload distribution system
JavaScript
6
star
64

bookspeak

Create audio books in any language using Python
Python
5
star
65

complexity_simulations

Code repository for 'Programming Complexity' talk
Go
5
star
66

Utopia2

Sugarscape JRuby simulation clone
Ruby
5
star
67

qard

Qard is a simple QR Code business card generator.
HTML
4
star
68

pompoko

Pom Poko is a sample blog web app written using the Tanuki web framework.
Ruby
4
star
69

mosaicgo

Docker-deployed version of the concurrent mosaic web application
Go
4
star
70

sausheong.github.com

Saush's GitHub Homepage
4
star
71

sgfireworks

Singapore 59th National Day ASCII art fireworks
Python
4
star
72

bots

Simple library for controlling robots using Ruby
Ruby
3
star
73

complexity

Programming Complexity
3
star
74

newspaper

Faster way to read print newspapers
Go
3
star
75

easywiki

An all-in-a-file wiki web app
Ruby
3
star
76

goauthserv

A REST-based authentication service written in Go.
Go
3
star
77

myhaze

Haze monitoring for Malaysia
JavaScript
3
star
78

gosearch

A simple search engine in Go
Go
3
star
79

pixelate

Simple Go web application that pixelates a given JPEG file
Go
3
star
80

go-recipes

Code for the Go Recipes publication site
Go
3
star
81

waldo

Waldo is a command-line AI assistant that wraps around local LLMs, Google's Gemini models and OpenAI's GPT models
Go
3
star
82

kaze

Kaze is a chatbot built in Go that embeds Ollama.
Go
3
star
83

culture_sim

Model and simulate cultural dissemination with Go and Python
Jupyter Notebook
2
star
84

todayreader

An alternative reader for the Today newspaper
CSS
2
star
85

hist

Simple Go web app to create histograms
HTML
2
star
86

sausheong.github.io

HTML
2
star
87

mst

Code repository for the minimum spanning tree algorithms post
Go
2
star
88

server-frameworks

Ruby
2
star
89

simplerd

Server for the Simpler Chrome extension
Go
2
star
90

programming_ai

Repository for scripts and code for Programming with AI
Python
2
star
91

gale

Gale is an AI chatbot used for question & answering over documents, built with Go
HTML
1
star
92

sghaze

Singapore Haze alert
JavaScript
1
star
93

perf-go

Testing the performance of the basic go web application
Ruby
1
star
94

tweetclone

Twitter clone
1
star
95

shado

Camera-based motion detector
Go
1
star
96

red

Go
1
star
97

minstrel

Using LLM to create stories
HTML
1
star
98

loca

Simple chatbot wrapping around a LLM.
Python
1
star
99

multipage-pdf

Sample code to show how to convert multi-page PDFs to a sequence of PNG images using RMagick
Ruby
1
star
100

multimodal

Experiments in comparing multi-modal LLMs
Metal
1
star