Skip to content

Commit

Permalink
Merge pull request hubotio#219 from github/connect-middleware
Browse files Browse the repository at this point in the history
working http interface
  • Loading branch information
tombell committed Jan 12, 2012
2 parents 4ab6bb7 + d780392 commit aa3134c
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 28 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ individual scripts.
[hubot-scripts]: https://github.com/github/hubot-scripts
[hubot-scripts-readme]: https://github.com/github/hubot-scripts#readme

## HTTP Listener

Hubot has a HTTP listener which listens on the port specified by the `PORT`
environment variable.

You can specify routes to listen on in your scripts by using the `router`
property on `robot`.

```coffeescript
module.exports = (robot) ->
robot.router.get "/hubot/version", (req, res) ->
res.end robot.version
```

There are functions for GET, POST, PUT and DELETE, which all take a route and
callback function that accepts a request and a response.

## Testing hubot locally

Install all of the required dependencies by running `npm install`.
Expand Down
22 changes: 10 additions & 12 deletions bin/hubot
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Switches = [
[ "-s", "--enable-slash", "Enable replacing the robot's name with '/' (deprecated)" ],
[ "-l", "--alias ALIAS", "Enable replacing the robot's name with alias" ],
[ "-n", "--name NAME", "The name of the robot in chat" ],
[ "-d", "--disable-httpd", "Disable the HTTP server" ],
[ "-v", "--version", "Displays the version of hubot installed" ]
]

Expand All @@ -28,6 +29,7 @@ Options =
create: false
adapter: "shell"
alias: false
enableHttpd: true

Parser = new OptParse.OptionParser(Switches)
Parser.banner = "Usage hubot [options]"
Expand All @@ -49,6 +51,9 @@ Parser.on "enable-slash", (opt) ->
Parser.on "alias", (opt, value) ->
Options.alias = value

Parser.on "disable-httpd", (opt) ->
Options.enableHttpd = false

Parser.on "help", (opt, value) ->
console.log Parser.toString()
process.exit 0
Expand All @@ -66,22 +71,15 @@ if Options.create
creator = new Creator.Creator(Options.path)
creator.run()

else if Options.version
package_path = __dirname + "/../package.json"

Fs.readFile package_path, (err,data) ->
if err
console.error "Could not open package file : %s", err
process.exit 1
else
adapterPath = Path.resolve __dirname, "..", "src", "adapters"

content = JSON.parse(data.toString('ascii'))
console.log content['version']
robot = Hubot.loadBot adapterPath, Options.adapter, Options.enableHttpd, Options.name

if Options.version
console.log robot.version
process.exit 0

else
adapterPath = Path.resolve __dirname, "..", "src", "adapters"
robot = Hubot.loadBot adapterPath, Options.adapter, Options.name
robot.enableSlash = Options.enableSlash
robot.alias = Options.alias

Expand Down
4 changes: 2 additions & 2 deletions index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
# YourBot = Hubot.robot 'campfire', 'yourbot'

# Loads a Hubot robot
exports.loadBot = (adapterPath, adapterName, botName) ->
exports.loadBot = (adapterPath, adapterName, enableHttpd, botName) ->
robot = require './src/robot'
new robot adapterPath, adapterName, botName
new robot adapterPath, adapterName, enableHttpd, botName

exports.robot = ->
require './src/robot'
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"coffee-script": "1.2.0",
"optparse": "1.0.3",
"scoped-http-client": "0.9.6",
"log": "1.2.0"
"log": "1.2.0",
"connect": "1.8.5"
},

"main": "./index",
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/shell.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Shell extends Adapter
@repl.on "line", (buffer) =>
@repl.close() if buffer.toLowerCase() is "exit"
@repl.prompt()
user = @userForId '1', name: "Shell"
user = @userForId '1', name: "Shell", room: "Shell"
@receive new Robot.TextMessage user, buffer

@repl.setPrompt "#{@robot.name}> "
Expand Down
86 changes: 75 additions & 11 deletions src/robot.coffee
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Fs = require 'fs'
Log = require 'log'
Path = require 'path'
Url = require 'url'
Fs = require 'fs'
Url = require 'url'
Log = require 'log'
Path = require 'path'
Connect = require 'connect'

Brain = require './brain'
User = require './user'
User = require './user'
Brain = require './brain'

HUBOT_DEFAULT_ADAPTERS = [ "campfire", "shell" ]

Expand All @@ -13,7 +14,7 @@ class Robot
# dispatch them to matching listeners.
#
# path - String directory full of Hubot scripts to load.
constructor: (adapterPath, adapter, name = "Hubot") ->
constructor: (adapterPath, adapter, httpd, name = "Hubot") ->
@name = name
@brain = new Brain
@alias = false
Expand All @@ -23,11 +24,21 @@ class Robot
@listeners = []
@loadPaths = []
@enableSlash = false

@logger = new Log process.env.HUBOT_LOG_LEVEL or "info"

@parseVersion()
@setupConnect() if httpd
@loadAdapter adapterPath, adapter if adapter?

# Public: Specify a router and callback to register as Connect middleware.
#
# route - A String of the route to match.
# callback - A Function that is called when the route is requested
#
# Returns nothing.
route: (route, callback) ->
@router.get route, callback

# Public: Adds a Listener that attempts to match incoming messages based on
# a Regex.
#
Expand Down Expand Up @@ -132,14 +143,50 @@ class Robot
for script in scripts
@loadFile path, script

# Setup the Connect server's defaults
#
# Sets up basic authentication if parameters are provided
#
# Returns: nothing.
setupConnect: ->
user = process.env.CONNECT_USER
pass = process.env.CONNECT_PASSWORD

@connect = Connect()

if user and pass
@connect.use Connect.basicAuth(user, path)

@connect.use Connect.bodyParser()
@connect.use Connect.router (app) =>

@router =
get: (route, callback) =>
@logger.debug "Registered route: GET #{route}"
app.get route, callback

post: (route, callback) =>
@logger.debug "Registered route: POST #{route}"
app.post route, callback

put: (route, callback) =>
@logger.debug "Registered route: PUT #{route}"
app.put route, callback

delete: (route, callback) =>
@logger.debug "Registered route: DELETE #{route}"
app.delete route, callback

@connect.listen process.env.PORT || 8080

# Load the adapter Hubot is going to use.
#
# path - A String of the path to adapter if local.
# adapter - A String of the adapter name to use.
#
# Returns nothing.
loadAdapter: (path, adapter) ->
@logger.info "Loading adapter #{adapter}"
@logger.debug "Loading adapter #{adapter}"

try
path = if adapter in HUBOT_DEFAULT_ADAPTERS
Expand Down Expand Up @@ -240,21 +287,38 @@ class Robot

matchedUsers

# Kick off the event loop for the adapter
#
# Returns: Nothing.
run: ->
@adapter.run()

# Public: Gracefully shutdown the robot process
#
# Returns: Nothing.
shutdown: ->
@adapter.close()
@brain.close()

# Public: The version of Hubot from npm
#
# Returns: SemVer compliant version number
parseVersion: ->
package_path = __dirname + "/../package.json"

data = Fs.readFileSync package_path, 'utf8'

content = JSON.parse data
@version = content.version

class Robot.Message
# Represents an incoming message from the chat.
#
# user - A User instance that sent the message.
constructor: (@user, @done = false) ->

# Indicates that no other Listener should be called on this object
finish: () ->
finish: ->
@done = true

class Robot.TextMessage extends Robot.Message
Expand Down Expand Up @@ -365,7 +429,7 @@ class Robot.Response
# Public: Tell the message to stop dispatching to listeners
#
# Returns nothing.
finish: () ->
finish: ->
@message.finish()

# Public: Creates a scoped http client with chainable methods for
Expand Down
16 changes: 16 additions & 0 deletions src/scripts/httpd.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# A simple interaction with the built in HTTP Daemon
spawn = require('child_process').spawn

module.exports = (robot) ->
robot.router.get "/hubot/version", (req, res) ->
res.end robot.version
robot.router.post "/hubot/ping", (req, res) ->
res.end "PONG"
robot.router.get "/hubot/time", (req, res) ->
res.end "Server time is: #{new Date()}"
robot.router.get "/hubot/info", (req, res) ->
child = spawn('/bin/sh', ['-c', "echo I\\'m $LOGNAME@$(hostname):$(pwd) \\($(git rev-parse HEAD)\\)"])

child.stdout.on 'data', (data) ->
res.end "#{data.toString().trim()} running node #{process.version} [pid: #{process.pid}]"
child.stdin.end()
2 changes: 1 addition & 1 deletion src/templates/Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
app: bin/hubot -a campfire -n Hubot
web: bin/hubot -a campfire -n Hubot
17 changes: 17 additions & 0 deletions src/templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ those dependencies are provided by [npm][npmjs].

[npmjs]: http://npmjs.org

## HTTP Listener

Hubot has a HTTP listener which listens on the port specified by the `PORT`
environment variable.

You can specify routes to listen on in your scripts by using the `router`
property on `robot`.

```coffeescript
module.exports = (robot) ->
robot.router.get "/hubot/version", (req, res) ->
res.end robot.version
```

There are functions for GET, POST, PUT and DELETE, which all take a route and
callback function that accepts a request and a response.

### Redis

If you are going to use the `redis-brain.coffee` script from `hubot-scripts`
Expand Down

0 comments on commit aa3134c

Please sign in to comment.