Skip to content
This repository has been archived by the owner on Aug 5, 2020. It is now read-only.

Commit

Permalink
Initial Commit of Meowbify
Browse files Browse the repository at this point in the history
 - Forked from our internal code
 - Added a README
 - Added a few cats

Meowbify goes open source!
  • Loading branch information
Ryan J Daw committed Jul 6, 2012
0 parents commit 5fd8bd0
Show file tree
Hide file tree
Showing 23 changed files with 1,786 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
node_modules/
*.swp
.DS_Store
*.bak

2 changes: 2 additions & 0 deletions Makefile
@@ -0,0 +1,2 @@
deps:
npm install .
1 change: 1 addition & 0 deletions Procfile
@@ -0,0 +1 @@
web: node web.js
64 changes: 64 additions & 0 deletions README.md
@@ -0,0 +1,64 @@
Meowbify
==============

Meowbify is a Node.js super-webscale kitten-powered Coffeescript-infused evented based streaming cat injecting proxy.

Think CloudFlare, but with Cats.

[Check it out live](http://www.meowbify.com/)


Meowbifiwhat is it?
--------------------

This proxy is designed to insert tags, and also rewrite links, headers, cookies, etc
so it appears to transparently work as a man in the middle.

It also inserts Cats, courtesy of [The Cat API](http://thecatapi.com/).

The proxy is designed to run on a wildcard domain *.SUFFIX_DOMAIN, and everything
are rewritten to match that domain.

To support HTTP and HTTPS sites, we use a prefix usually cat.* or cats.*.

It currently only support origin sites running on ports :80 and :443.


Configuration
--------------

These can be set by environment variables:

*PORT*
: Port proxy listens on
Default: 5000

*EXTERNAL_PORT*
: Port proxy appears to be listening on (eg, the front-end server)
Default: 80

*SUFFIX_DOMAIN*
: Domain that is appended when rewritting links
Default: 'meowbify.com'

*PREFIX_SUBDOMAIN*
: The subdomain that is prefix to mark http and https sites.
Default: "cat" (which means cat.* == http, cats.* == https).

*LOG_FORMAT*
: Connect Middleware style log format
Default: ':method :status :response-time \t:req[Host]:url :user-agent'


Special Thanks
--------------

The Mobify Team:

- @fractaltheory who christened it "Meowbify"
- @kpeatt who butched^Hbeautifully altered Mobify's logo in to a Cat
- @shawnjan8 who squatted the domain
- @rrjamie who wasted several afternoons of time to write it
- @mobify whose time and money @rrjamie was wasting

Also thanks to @AdenForshaw, who built the CatAPI.
404 changes: 404 additions & 0 deletions kitties.txt

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions package.json
@@ -0,0 +1,21 @@
{
"author": "Mobify <dev@mobify.com> (https://github.com/mobify)",
"name": "meowbify",
"description": "Injects Cats in to Pages",
"version": "0.0.1",
"homepage": "https://www.meowbify.com/",
"main": "lib/main.js",
"dependencies": {
"htmlparser": "git+https://github.com/mobify/node-htmlparser.git#v1.x",
"coffee-script": "~1.3.3",
"request": "~2.9.202",
"connect": "2.3.3",
"underscore": "~1.3.3"
},
"devDependencies": {},
"optionalDependencies": {},
"engines": {
"node": "0.6.x"
},
"private": true
}
221 changes: 221 additions & 0 deletions src/bufferstream.coffee
@@ -0,0 +1,221 @@
Stream = require 'stream'

###
BufferStream
A streaming interface for a buffer.
You can stream (eg, `pipe`) data in to a `BufferStream` object
and then `pipe` it into another object.
BufferStreams have a few benefits:
- Automatically buffer any amount of data (memory permitting).
- Automatically increase in size in a semi-intelligent fashion.
- No need to wait for `drain` on the write side.
- Makes efficient use of buffers
- You can `pause` a BufferStream and collect data for later.
###

class BufferStream extends Stream
MIN_BUFFER_SIZE = 4096
MAX_BUFFER_SIZE = MIN_BUFFER_SIZE * 16

constructor: (size = MIN_BUFFER_SIZE) ->
@writeIndex = 0
@readIndex = 0
@buffer = new Buffer(size)

@readBuffer = 1024*16

@encoding = 'utf8'
@emitStrings = false


# Writing
@_endWrite = false
@_endRead = false

@_destroySoon = false
@_destroyed = false

@writable = true
@readable = true

@paused = false


###
#
###
ensureSpace: (bytes) ->
bytesAvailable = @buffer.size - @writeIndex

# Current buffer might be large enough
if bytesAvailable >= bytes
return

# Allocate new buffer
currentSize = @writeIndex - @readIndex
desiredSize = currentSize + bytes

targetSize = MIN_BUFFER_SIZE
while targetSize < desiredSize
targetSize *= 2

# Copying to a new buffer prevents mangling of buffers
# that were returned by _read.
newBuffer = new Buffer targetSize
@buffer.copy newBuffer, 0, @readIndex, @writeIndex
@writeIndex = @writeIndex - @readIndex
@readIndex = 0

@buffer = newBuffer

true

###
# Internal Buffer Access
###
_writeBuffer: (buffer) ->
@ensureSpace buffer.length
buffer.copy @buffer, @writeIndex
@writeIndex = @writeIndex + buffer.length

true

_writeString: (string) ->
bytes = Buffer.byteLength string, @encoding
@ensureSpace bytes

@buffer.write string, @writeIndex, bytes, @encoding
@writeIndex = @writeIndex + bytes

true

_readBuffer: (maxBytes = 0) ->
if maxBytes == 0
@targetIndex = @writeIndex
else
@targetIndex = @readIndex + maxBytes

if @targetIndex > @writeIndex
@targetIndex = @writeIndex

buffer = @buffer.slice @readIndex, @targetIndex
@readIndex = @targetIndex

buffer

_getLength: () ->
@writeIndex - @readIndex


###
# Readable Stream Methods
###
setEncoding: (encoding) ->
if encoding != 'utf8'
throw new Error "Only UTF8 is a supported encoding"

throw new Error "Not Implemented."
@encoding = encoding
@emitStrings = true

pause: () ->
@paused = true

resume: () ->
@paused = false
@flush()


_flush: () ->
empty = !@_getLength()

if @paused or @_destroyed
return

# Flush some data
@_emitData(@_endWrite or @_destroySoon)

# Trigger End Event
if empty and @_endWrite
@_emitEnd()

if empty and @_endWrite and @_destroySoon
@destroy()

_emitData: (force = false) ->
bytesRemaining = @_getLength()

if (bytesRemaining >= MIN_BUFFER_SIZE) or force
data = @_readBuffer MAX_BUFFER_SIZE
if data.length
@emit "data", data

# There may be more data
@flush()

_emitEnd: () ->
@readable = false
@_endRead = false
@emit "end"
@destroy()

###
# Writable Stream Methods
###

write: (data, encoding = 'utf8') ->
if encoding != 'utf8'
throw new Error "Only supports utf8."

if @_endWrite or @_destroyed
throw new Error "Cannot write to stream, has been ended or destroyed."

if Buffer.isBuffer data
@_writeBuffer data
else
# String
@_writeString data, encoding

@flush()
true

end: (data, encoding) ->
if data?
@write data, encoding

@writable = false
@_endWrite = true
@flush()

###
# Readable, Writable Stream Methods
###
flush: () ->
process.nextTick () =>
@_flush()


destroy: () ->
@_destroyed = true
@writable = false
@readable = false
if !@_endRead
@emit "close"
@cleanup()

destroySoon: () ->
@end()
@_destroySoon = true
@flush()

cleanup: () ->
@readIndex = 0
@writeIndex = 0
@buffer = null

module.exports = BufferStream
69 changes: 69 additions & 0 deletions src/capture.coffee
@@ -0,0 +1,69 @@
Connect = require 'connect'

BufferStream = require './bufferstream'



captureResponse = (res, onEndHeader) ->
# Hold on to original functions
write = res.write
end = res.end
writeHead = res.writeHead
setHeader = res.setHeader

res.reason = ""
res.headers = {}

# Create paused buffer to collect data
buffer = new BufferStream()

headersWritten = false

res.write = (data, encoding) ->
buffer.write data, encoding

if not headersWritten and onEndHeader
headersWritten = true
onEndHeader res.statusCode, res.reason, res.headers

res.end = (data, encoding) ->
buffer.end data, encoding

if not headersWritten and onEndHeader
headersWritten = true
onEndHeader res.statusCode, res.reason, res.headers

res.writeHead = (_statusCode, _reason..., _headers) ->
statusCode = _statusCode
res.reason = _reason[0] or res.reason
res.headers = _headers or res.headers

writeHead.apply res, arguments

if not headersWritten and onEndHeader
headersWritten = true
onEndHeader res.statusCode, res.reason, res.headers

res.setHeader = (header, value) ->
res.headers[header] = value



newRes =
write: (data, encoding) ->
write.call res, data, encoding
end: (data, encoding) ->
end.call res, data, encoding
on: () ->
res.on.apply res, arguments
emit: () ->
res.emit.apply res, arguments
writable: true
removeListener: () ->
res.removeListener.apply res, arguments

return [buffer, newRes]


module.exports = captureResponse

0 comments on commit 5fd8bd0

Please sign in to comment.