├── .gitignore ├── lib ├── gearman.js ├── packet │ ├── priorities.js │ ├── types.js │ ├── requests.js │ └── responses.js ├── util.js ├── client │ └── responses.js ├── job.js ├── packet.js └── client.js ├── HISTORY.md ├── examples └── reverse-client.js ├── test ├── fixtures │ └── worker.rb ├── test-client.js ├── test-job.js └── test-packet.js ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /lib/gearman.js: -------------------------------------------------------------------------------- 1 | var Client = require("./client").Client; 2 | 3 | exports.Client = Client; 4 | exports.Job = require("./job").Job; 5 | exports.createClient = function (port, host) { return new Client(port, host); }; 6 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## 0.3.0 / 2011-05-02 4 | 5 | * Background job support 6 | 7 | ## 0.2.0 / 2011-04-15 8 | 9 | * Foreground job priority support 10 | 11 | ## 0.1.0 / 2011-04-12 12 | 13 | * Initial Release 14 | -------------------------------------------------------------------------------- /examples/reverse-client.js: -------------------------------------------------------------------------------- 1 | var gearman = require("gearman"), 2 | client = gearman.createClient(); 3 | 4 | console.log("Sending job..."); 5 | var job = client.submitJob("reverse", "Hello World!", { encoding: "utf8" }); 6 | job.on("complete", function (result) { 7 | console.log(result); 8 | client.end(); 9 | }); 10 | -------------------------------------------------------------------------------- /lib/packet/priorities.js: -------------------------------------------------------------------------------- 1 | // Maps priority names to request types 2 | exports.names = { 3 | low: "SUBMIT_JOB_LOW", 4 | normal: "SUBMIT_JOB", 5 | high: "SUBMIT_JOB_HIGH" 6 | }; 7 | 8 | exports.types = {}; 9 | 10 | for (var n in exports.names) { 11 | if (Object.prototype.hasOwnProperty.call(exports.names, n)) { 12 | exports.types[exports.names[n]] = n; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | // Utility functions 2 | 3 | var gearman = require("./gearman"); 4 | 5 | // Extend an object. 6 | exports.extend = function (dst, src) { 7 | for (var p in src) { if (Object.prototype.hasOwnProperty.call(src, p)) { 8 | dst[p] = src[p]; 9 | }} 10 | return dst; 11 | }; 12 | 13 | // Debug messages 14 | exports.debug = function () { 15 | if (gearman.debug) { console.warn.apply(console, arguments); } 16 | }; 17 | -------------------------------------------------------------------------------- /test/fixtures/worker.rb: -------------------------------------------------------------------------------- 1 | # This is an example worker that the tests use. `gem install gearman` and run 2 | # `gearmand` on localhost:4730 to use it. 3 | 4 | require 'rubygems' 5 | require 'gearman' 6 | 7 | w = Gearman::Worker.new(['localhost:4730']) 8 | 9 | w.add_ability('test') do |data, job| 10 | sleep 0.25 11 | job.send_data 'test' 12 | sleep 0.25 13 | job.report_warning 'test' 14 | sleep 0.25 15 | data.reverse 16 | end 17 | 18 | w.add_ability('test_fail') do 19 | sleep 0.25 20 | false 21 | end 22 | 23 | loop { w.work } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gearman", 3 | "version": "0.3.0", 4 | "description": "Client library for Gearman", 5 | "keywords": ["gearman", "job", "worker", "background"], 6 | "homepage": "https://github.com/cramerdev/gearman-node", 7 | "author": "Nathan L Smith ", 8 | "main": "lib/gearman", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/cramerdev/gearman-node.git" 12 | }, 13 | "dependencies": { "binary": ">=0.0.0" }, 14 | "devDependencies": { "nodeunit": "~0.4" } 15 | } 16 | -------------------------------------------------------------------------------- /lib/packet/types.js: -------------------------------------------------------------------------------- 1 | exports.names = { 2 | SUBMIT_JOB: 7, 3 | SUBMIT_JOB_HIGH: 21, 4 | SUBMIT_JOB_LOW: 33, 5 | SUBMIT_JOB_BG: 18, 6 | SUBMIT_JOB_HIGH_BG: 32, 7 | SUBMIT_JOB_LOW_BG: 34, 8 | GET_STATUS: 15, 9 | STATUS_RES: 20, 10 | JOB_CREATED: 8, 11 | WORK_COMPLETE: 13, 12 | WORK_FAIL: 14, 13 | WORK_EXCEPTION: 25, 14 | WORK_DATA: 28, 15 | WORK_WARNING: 29 16 | }; 17 | 18 | exports.numbers = {}; 19 | 20 | // and the inverse 21 | for (var n in exports.names) { 22 | if (Object.prototype.hasOwnProperty.call(exports.names, n)) { 23 | exports.numbers[exports.names[n]] = n; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Nathan L Smith 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /lib/packet/requests.js: -------------------------------------------------------------------------------- 1 | // These functions implement the required parts of each packet type 2 | 3 | var binary = require("binary"); 4 | 5 | function submitJob(options) { 6 | options = options || {}; 7 | var data = options.data || 0, 8 | name = options.name, 9 | encoding = options.encoding; 10 | 11 | if (typeof name !== "string") { 12 | throw Error("function name must be a string"); 13 | } 14 | if (data !== 0 && typeof encoding === "string") { 15 | data = new Buffer(data, encoding); 16 | } else { 17 | data = new Buffer(data); 18 | } 19 | 20 | return binary.put(). 21 | put(new Buffer(name, "ascii")). 22 | word8(0). 23 | word8(0). // TODO: unique client ids 24 | put(data). 25 | buffer(); 26 | } 27 | 28 | module.exports = { 29 | SUBMIT_JOB: submitJob, 30 | SUBMIT_JOB_HIGH: submitJob, 31 | SUBMIT_JOB_LOW: submitJob, 32 | SUBMIT_JOB_BG: submitJob, 33 | SUBMIT_JOB_HIGH_BG: submitJob, 34 | SUBMIT_JOB_LOW_BG: submitJob, 35 | GET_STATUS: function (options) { 36 | return new Buffer((options || {}).handle, "ascii"); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /lib/client/responses.js: -------------------------------------------------------------------------------- 1 | // Response handlers for the client 2 | 3 | // Returns a handler that emits data for a job 4 | function jobEmitData(event) { 5 | return function (data, client) { 6 | var job = client.jobs[data.handle], 7 | d = data.data; 8 | 9 | if ("encoding" in job) { d = d.toString(job.encoding); } 10 | job.emit(event, d); 11 | }; 12 | } 13 | 14 | module.exports = { 15 | JOB_CREATED: function (data, client) { 16 | var job = client.lastJobSubmitted, 17 | handle = data.handle; 18 | 19 | client.jobs[handle] = job; 20 | job.handle = handle; 21 | job.emit("create", handle); 22 | }, 23 | WORK_COMPLETE: jobEmitData("complete"), 24 | WORK_FAIL: function (data, client) { 25 | var handle = data.handle, 26 | job = client.jobs[handle]; 27 | job.emit("fail"); 28 | }, 29 | WORK_DATA: jobEmitData("data"), 30 | WORK_WARNING: jobEmitData("warning"), 31 | // Get a STATUS_RES response, find the jobs, and execute their callbacks 32 | STATUS_RES: function (data, client) { 33 | data = data || {}; 34 | var job = client.jobs[data.handle]; 35 | delete data.type; 36 | 37 | if (job) { job.statusCallbacks.forEach(function (callback) { 38 | callback(data); 39 | }); } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /lib/job.js: -------------------------------------------------------------------------------- 1 | var gearman = require("gearman"), 2 | inherits = require("util").inherits, 3 | util = require("./util"), 4 | extend = util.extend, 5 | debug = util.debug, 6 | packet = require("./packet"), 7 | priorities = require("./packet/priorities").names, 8 | EventEmitter = require("events").EventEmitter, 9 | Job; 10 | 11 | Job = function (options) { 12 | if (!(this instanceof Job)) { return new Job(options); } 13 | extend(this, options); 14 | this.client = this.client || gearman.createClient(); 15 | this.priority = this.priority || "normal"; 16 | 17 | // Array of callbacks waiting for status 18 | this.statusCallbacks = []; 19 | }; 20 | inherits(Job, EventEmitter); 21 | exports.Job = Job; 22 | 23 | Job.prototype.submit = function () { 24 | var client = this.client, 25 | data = { 26 | name: this.name, 27 | data: this.data, 28 | encoding: this.encoding 29 | }; 30 | 31 | // Set the type given the priority 32 | if (!(this.priority in priorities)) { throw Error("invalid priority"); } 33 | data.type = priorities[this.priority]; 34 | 35 | // Append _BG to background jobs' type 36 | if (this.background) { data.type += "_BG"; } 37 | 38 | client.getConnection().write(packet.encode(data), this.encoding); 39 | debug("Sent:", data); 40 | client.lastJobSubmitted = this; 41 | }; 42 | 43 | Job.prototype.getStatus = function (callback) { 44 | var data = { type: "GET_STATUS", handle: this.handle }; 45 | this.client.getConnection().write(packet.encode(data)); 46 | debug("Sent:", data); 47 | if (typeof callback === "function") {this.statusCallbacks.push(callback); } 48 | }; 49 | -------------------------------------------------------------------------------- /lib/packet/responses.js: -------------------------------------------------------------------------------- 1 | // functions used for multiple response types 2 | 3 | var binary = require("binary"), 4 | extend = require("../util").extend, 5 | nb = new Buffer([0]); // null buffer 6 | 7 | function resHandle(object) { 8 | object = object || {}; 9 | var data = object.inputData; 10 | if (data instanceof Buffer) { 11 | object.handle = data.toString(); 12 | } 13 | return object; 14 | } 15 | 16 | function resHandleAndData(object) { 17 | object = object || {}; 18 | var data = object.inputData, 19 | o = {}, 20 | size = 0; 21 | 22 | o = binary.parse(data). 23 | scan("handle", nb). 24 | tap(function (vars) { 25 | size = data.length - vars.handle.length - 1; 26 | }). 27 | buffer("data", size). 28 | vars; 29 | 30 | return extend({ 31 | handle: o.handle.toString(), 32 | data: o.data 33 | }, object); 34 | } 35 | 36 | module.exports = { 37 | JOB_CREATED: resHandle, 38 | WORK_COMPLETE: resHandleAndData, 39 | WORK_FAIL: resHandle, 40 | WORK_EXCEPTION: resHandleAndData, 41 | WORK_DATA: resHandleAndData, 42 | WORK_WARNING: resHandleAndData, 43 | STATUS_RES: function (object) { 44 | object = object || {}; 45 | var o = {}; 46 | 47 | o = binary.parse(object.inputData). 48 | scan("handle", nb). 49 | scan("known", nb). 50 | scan("running", nb). 51 | scan("num", nb). 52 | word8be("den"). 53 | vars; 54 | 55 | return extend({ 56 | handle: o.handle.toString(), 57 | known: !!o.known[0], 58 | running: !!o.running[0], 59 | percentComplete: [o.num[0], o.den] 60 | }, object); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /lib/packet.js: -------------------------------------------------------------------------------- 1 | // Encoder/decoder for binary data 2 | 3 | var binary = require("binary"), 4 | types = require("./packet/types"), 5 | requests = require("./packet/requests"), 6 | responses = require("./packet/responses"), 7 | req = new Buffer("REQ", "ascii"), 8 | res = new Buffer([0, 0x52, 0x45, 0x53]); // \0RES 9 | 10 | 11 | // Takes an options object and builds a gearman packet 12 | exports.encode = function (options) { 13 | options = options || {}; 14 | var type = options.type, 15 | p; 16 | 17 | if (!(type in types.names)) { 18 | throw Error("unknown request type"); 19 | } 20 | 21 | p = requests[type](options); 22 | 23 | return binary.put(). 24 | word8(0). 25 | put(req). 26 | word32be(types.names[type]). 27 | word32be(p.length). 28 | put(p). 29 | buffer(); 30 | }; 31 | 32 | 33 | // Takes a buffer and converts it to an object to be used 34 | exports.decode = function (buf) { 35 | var o, data, size; 36 | 37 | if (!(buf instanceof Buffer)) { 38 | throw Error("input must be a Buffer"); 39 | } 40 | 41 | o = binary.parse(buf). 42 | word32bu("reqType"). 43 | word32bu("type"). 44 | word32bu("size"). 45 | tap(function (vars) { size = vars.size; }). 46 | buffer("inputData", size). 47 | vars; 48 | 49 | // test if reqtype is valid, Buffer.compare? 50 | for (var i = 0; i < o.reqType.length; i += 1) { 51 | if (o.reqType[i] !== req[i]) { 52 | throw Error("invalid request header"); 53 | } 54 | } 55 | o.type = types.numbers[o.type]; 56 | if (!o.type) { throw Error("must have a valid type"); } 57 | 58 | // size is required 59 | size = parseInt(o.size, 10); 60 | if (isNaN(size) || size < 0) { 61 | throw Error("packet length not sent"); 62 | } 63 | 64 | o = responses[o.type](o); 65 | 66 | ["reqType", "size", "inputData"].forEach(function (prop) { 67 | delete o[prop]; 68 | }); 69 | 70 | return o; 71 | }; 72 | -------------------------------------------------------------------------------- /test/test-client.js: -------------------------------------------------------------------------------- 1 | var gearman = require("gearman"), 2 | Client = gearman.Client, 3 | Job = gearman.Job, 4 | Socket = require("net").Socket, 5 | testCase = require("nodeunit").testCase, 6 | client; 7 | 8 | // gearman.debug = true; 9 | 10 | client = gearman.createClient(); 11 | 12 | module.exports = testCase({ 13 | "createClient": function (test) { 14 | var otherClient; 15 | 16 | test.ok(client instanceof Client, 17 | "gearman.createClient() creates a client"); 18 | test.equal(client.port, 4730, "default port 4730"); 19 | test.equal(client.host, "localhost", "default host 'localhost'"); 20 | 21 | otherClient = gearman.createClient(1234, "example.com"); 22 | 23 | test.equal(otherClient.port, 1234, "port argument 1234"); 24 | test.equal(otherClient.host, "example.com", "host argument 'example.com'"); 25 | 26 | test.done(); 27 | }, 28 | 29 | "getConnection": function (test) { 30 | test.ok(client.getConnection() instanceof Socket, 31 | "client.getConnection() returns a Socket"); 32 | test.done(); 33 | }, 34 | 35 | "submitJob": function (test) { 36 | test.ok(client.submitJob("test") instanceof Job, 37 | "client.submitJob() creates a job"); 38 | test.done(); 39 | }, 40 | 41 | "end": function (test) { 42 | test.ok(typeof client.end === "function", 43 | "client.end() method exists"); 44 | test.done(); 45 | }, 46 | 47 | "getJobStatus": function (test) { 48 | var job = client.submitJob("test", "test", { background: true }); 49 | test.ok(typeof client.getJobStatus === "function", 50 | "client.getJobStatus is a function"); 51 | 52 | job.on("create", function (handle) { 53 | client.getJobStatus(handle, function (status) { 54 | test.deepEqual(status, { handle: handle, 55 | known: true, 56 | running: true, 57 | percentComplete: [ 48, 48 ] }); 58 | test.done(); 59 | }); 60 | }); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | var gearman = require("./gearman"), 2 | Job = require("./job").Job, 3 | packet = require("./packet"), 4 | util = require("./util"), 5 | extend = util.extend, 6 | debug = util.debug, 7 | responses = require("./client/responses"), 8 | net = require("net"), 9 | Client, 10 | defaults = { host: "localhost", port: 4730 }; 11 | 12 | Client = function (port, host) { 13 | if (!(this instanceof Client)) { return new Client(port, host); } 14 | extend(this, { 15 | port: Number(port) || defaults.port, 16 | host: host || defaults.host, 17 | jobs: {}, 18 | lastJobSubmitted: null, 19 | connection: null 20 | }); 21 | }; 22 | exports.Client = Client; 23 | 24 | // Creates the client connection if there isn't one, opens the connection if 25 | // it's closed, and sets up listeners. Returns the connection object 26 | Client.prototype.getConnection = function () { 27 | var conn = this.connection, 28 | client = this; 29 | 30 | if (!conn) { 31 | conn = net.createConnection(this.port, this.host); 32 | 33 | conn.on("data", function (data) { 34 | // decode the data and execute the proper response handler 35 | data = packet.decode(data); 36 | var type = data.type; 37 | debug("Recieved:", data); 38 | if (type in responses) { responses[type](data, client); } 39 | }); 40 | } 41 | 42 | this.connection = conn; 43 | return conn; 44 | }; 45 | 46 | // Close connections 47 | Client.prototype.end = function () { 48 | var conn = this.connection; 49 | if (conn) { conn.end(); } 50 | }; 51 | 52 | // Submit a job 53 | Client.prototype.submitJob = function (name, data, options) { 54 | options = extend({ 55 | name: name, 56 | data: data, 57 | client: this 58 | }, options || {}); 59 | 60 | var job = new Job(options); 61 | job.submit(); 62 | return job; 63 | }; 64 | 65 | // Get a job's status from its handle 66 | Client.prototype.getJobStatus = function (handle, callback) { 67 | var job = this.jobs[handle]; 68 | 69 | // If we don't have the job, create it 70 | if (!job) { 71 | job = new Job({ client: this, handle: handle, background: true }); 72 | } 73 | job.getStatus(callback); 74 | }; 75 | -------------------------------------------------------------------------------- /test/test-job.js: -------------------------------------------------------------------------------- 1 | var gearman = require("gearman"), 2 | Job = gearman.Job, 3 | EventEmitter = require("events").EventEmitter, 4 | testCase = require("nodeunit").testCase, 5 | client, job; 6 | 7 | // XXX: These need a real gearman server running on localhost:4730 and 8 | // test/fixtures/worker.rb running. Need to make a mock server or something. 9 | 10 | // gearman.debug = true; 11 | 12 | client = gearman.createClient(); 13 | job = client.submitJob("test", "test", { encoding: "utf8" }); 14 | 15 | module.exports = testCase({ 16 | "Job": function (test) { 17 | test.ok(job instanceof EventEmitter, 18 | "Job instances are EventEmitters"); 19 | test.equal("normal", job.priority, "default priority is normal"); 20 | test.done(); 21 | }, 22 | 23 | "submit": function (test) { 24 | job.on("create", function (handle) { 25 | test.ok(typeof handle === "string", 26 | "handle returned on create event"); 27 | test.equal(job.handle, handle, 28 | "job handle assigned on create event"); 29 | test.done(); 30 | }); 31 | }, 32 | 33 | "submit { priority: 'high' }": function (test) { 34 | var job = client.submitJob("test", "test", { encoding: "utf8", 35 | priority: "high" }); 36 | job.on("create", function (handle) { 37 | test.ok(typeof handle === "string", 38 | "handle returned on create event"); 39 | test.equal(job.handle, handle, 40 | "job handle assigned on create event"); 41 | test.done(); 42 | }); 43 | }, 44 | 45 | "submit { priority: 'low' }": function (test) { 46 | var job = client.submitJob("test", "test", { encoding: "utf8", 47 | priority: "low" }); 48 | job.on("create", function (handle) { 49 | test.ok(typeof handle === "string", 50 | "handle returned on create event"); 51 | test.equal(job.handle, handle, 52 | "job handle assigned on create event"); 53 | test.done(); 54 | }); 55 | }, 56 | 57 | "submit { priority: 'invalid' }": function (test) { 58 | test.throws(function () { 59 | var job = client.submitJob("test", "test", { encoding: "utf8", 60 | priority: "other" }); 61 | }, "must have a known priority"); 62 | test.done(); 63 | }, 64 | 65 | "submit { background: true }": function (test) { 66 | var job = client.submitJob("test", "test", { background: true }); 67 | 68 | job.on("create", function (handle) { 69 | job.getStatus(function (status) { 70 | test.deepEqual(status, { handle: handle, 71 | known: true, 72 | running: true, 73 | percentComplete: [ 48, 48 ] }); 74 | test.done(); 75 | }); 76 | }); 77 | }, 78 | 79 | "submit { background: true, priority: 'high' }": function (test) { 80 | var job = client.submitJob("test", "test", { background: true, 81 | priority: "high" }); 82 | 83 | job.on("create", function (handle) { 84 | job.getStatus(function (status) { 85 | test.deepEqual(status, { handle: handle, 86 | known: true, 87 | running: true, 88 | percentComplete: [ 48, 48 ] }); 89 | test.done(); 90 | }); 91 | }); 92 | }, 93 | 94 | "submit { background: true, priority: 'low' }": function (test) { 95 | var job = client.submitJob("test", "test", { background: true, 96 | priority: "low" }); 97 | 98 | job.on("create", function (handle) { 99 | job.getStatus(function (status) { 100 | test.deepEqual(status, { handle: handle, 101 | known: true, 102 | running: true, 103 | percentComplete: [ 48, 48 ] }); 104 | test.done(); 105 | }); 106 | }); 107 | }, 108 | 109 | 110 | "event: data": function (test) { 111 | job.on("data", function (result) { 112 | test.equal("test", result, "work data received"); 113 | test.done(); 114 | }); 115 | }, 116 | 117 | "event: warning": function (test) { 118 | job.on("warning", function (warning) { 119 | test.equal("test", warning, "work warning received"); 120 | test.done(); 121 | }); 122 | }, 123 | 124 | "event: complete": function (test) { 125 | job.on("complete", function (result) { 126 | test.equal("tset", result, "work completes"); 127 | test.done(); 128 | }); 129 | }, 130 | 131 | "event: fail": function (test) { 132 | var failJob = client.submitJob("test_fail"); 133 | failJob.on("fail", function () { 134 | test.ok(true, true, "work fails"); 135 | test.done(); 136 | }); 137 | } 138 | }); 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT IS NO LONGER MAINTAINED. TRY [gearman-js](https://github.com/mreinstein/gearman-js)! 2 | 3 | # gearman-node 4 | 5 | This module lets you create [Gearman](http://gearman.org/) clients with [Node.js](http://nodejs.org/). 6 | 7 | Only a subset of the features of Gearman are currently implemented. 8 | 9 | ## Installation 10 | 11 | $ npm install gearman 12 | 13 | ## Usage 14 | 15 | ### Clients 16 | 17 | Creating a client goes something like this: 18 | 19 | var gearman = require("gearman"), 20 | client = gearman.createClient(4730, "my-gearman-server.example.com"); 21 | 22 | console.log("Sending job..."); 23 | var job = client.submitJob("reverse", "Hello World!", { encoding: "utf8" }); 24 | job.on("complete", function (data) { 25 | console.log(data); 26 | client.end(); 27 | }); 28 | 29 | This creates a client with a job and a listener for the result. 30 | 31 | You can run this on the command line like so: 32 | 33 | $ node reverse-client.js 34 | Sending job... 35 | !dlroW olleH 36 | 37 | ### More 38 | 39 | Additional Gearman tutorials and help can be found at [Gearman HQ help](http://gearmanhq.com/help/). 40 | 41 | ## API 42 | 43 | The `gearman` module contains methods for creating clients. You can include this module with `require('gearman')`. 44 | 45 | ### gearman.createClient([port = 4730], [host = 'localhost']) 46 | 47 | Creates a new Gearman client. Takes `port` and `host` arguments which default to `localhost:4730`. 48 | 49 | ### gearman.Client 50 | 51 | This is an object with methods to create and manage jobs. 52 | 53 | #### client.getConnection() 54 | 55 | Creates and sets up a client's connection (an instance of `net.Socket`) if it has not yet been created and returns it. 56 | 57 | #### client.end() 58 | 59 | Closes the client connections for when you don't need to use the client any more. 60 | 61 | #### client.submitJob(name, [data], [options]) 62 | 63 | Submits a job to a manager and returns a `gearman.Job`. `data` defaults to a `Buffer`, but can be a String if the `encoding` option is set to `'ascii'`, `'utf8'`, or `'base64'`. 64 | 65 | `options` is an object with the following defaults: 66 | 67 | { background: false, 68 | priority: 'normal', 69 | encoding: null 70 | } 71 | 72 | `priority` can be one of `'low'`, `'normal'`, or `'high'`. 73 | 74 | If `background` is set to `true`, the job is detached after the `create` event and no further events are emitted. 75 | 76 | #### client.getJobStatus(handle, [callback]) 77 | 78 | Works the same as `job.getStatus` but takes a job handle (assigned previously by the server for a job submitted with `background: true`) and executes a callback taking an object with status information. 79 | 80 | ### gearman.Job 81 | 82 | An object representing a job that has been submitted. `gearman.Job` instances are EventEmitters with the these events: 83 | 84 | #### Event: 'create' 85 | 86 | `function (handle) {}` 87 | 88 | Emitted when a job is created. `handle` is the new job's handle, which is also assigned to the `handle` property of the `gearman.Job` instance. 89 | 90 | #### Event: 'data' 91 | 92 | `function (data) {}` 93 | 94 | Emitted when data for the job is received. `data` is the data sent, as a `Buffer` or as a String if `encoding` was set before the job was submitted. 95 | 96 | #### Event: 'warning' 97 | 98 | `function (warning) {}` 99 | 100 | Same as a `data` event, but should be treated as a warning. 101 | 102 | #### Event: 'complete' 103 | 104 | `function (data) {}` 105 | 106 | Emitted when a job completes. `data` is the data sent, as a `Buffer` or as a String if `encoding` was set before the job was submitted. 107 | 108 | #### Event: 'fail' 109 | 110 | `function () {}` 111 | 112 | Emitted when a job fails. 113 | 114 | #### job.getStatus([callback]) 115 | 116 | For a job that was submitted in the background (with `background: true`), get information about its status. `callback` will be called when the server returns the status, with an object showing status information: 117 | 118 | job.getStatus(function (status) { console.dir(status); }); 119 | 120 | The `status` object returned can contain the following: 121 | 122 | { handle: String, // the job's handle 123 | known: Boolean, // is the job known? 124 | running: Boolean, // is the job running? 125 | percentComplete: [Number, Number] // Numerator & denominator of percentage complete 126 | } 127 | 128 | ## Tests 129 | 130 | To run the tests: 131 | 132 | Set up [nodeunit](https://github.com/caolan/nodeunit): 133 | 134 | $ npm link 135 | 136 | Some of the tests require a live Gearman server running on localhost:4730 (no mock server here, we keep it real.) [Download, install, and run](http://gearman.org/index.php?id=download#gearmand_c) `gearmand`. You can do `brew install gearman` on a Mac with [HomeBrew](http://mxcl.github.com/homebrew/). 137 | 138 | A worker used by some of the tests is in the test/fixtures directory. You'll need the `gearman` gem installed and you can run it with: 139 | 140 | $ ruby test/fixtures/worker.rb & 141 | 142 | Run the tests: 143 | 144 | $ nodeunit test 145 | 146 | ## Compatibility 147 | 148 | Should be compatible with node 0.4.x. 149 | 150 | Should work with Gearman 0.20 and [Gearman HQ](http://gearmanhq.com/). 151 | 152 | ## Contributors 153 | 154 | Thanks to the Gearman community and rest of the Gearman HQ team for help and documentation. This module is mostly based on [gearman-ruby](https://github.com/gearman-ruby/gearman-ruby) and [gearman.net](https://launchpad.net/gearman.net). 155 | 156 | Thanks to the Node.js community for excellent people, tools, resources, examples, documentation, and inspiration. 157 | 158 | ## License 159 | 160 | Copyright (c) 2011 Nathan L Smith 161 | 162 | Permission is hereby granted, free of charge, to any person obtaining a copy 163 | of this software and associated documentation files (the "Software"), to deal 164 | in the Software without restriction, including without limitation the rights 165 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 166 | copies of the Software, and to permit persons to whom the Software is 167 | furnished to do so, subject to the following conditions: 168 | 169 | The above copyright notice and this permission notice shall be included in 170 | all copies or substantial portions of the Software. 171 | 172 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 173 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 174 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 175 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 176 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 177 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 178 | THE SOFTWARE. 179 | -------------------------------------------------------------------------------- /test/test-packet.js: -------------------------------------------------------------------------------- 1 | var packetTypes = require("../lib/packet/types"), 2 | packet = require("../lib/packet"); 3 | 4 | exports["packet-types"] = function (test) { 5 | test.equal(packetTypes.names.SUBMIT_JOB, 7, "packet type names are loaded"); 6 | test.equal(packetTypes.numbers[7], "SUBMIT_JOB", "packet type numbers are loaded"); 7 | test.done(); 8 | }; 9 | 10 | exports["encode"] = function (test) { 11 | test.ok(typeof packet.encode === "function", "is a function"); 12 | test.ok(packet.encode({ type: "SUBMIT_JOB", name: "test" }) instanceof Buffer, "returns a Buffer"); 13 | test.throws(function () { packet.encode({ type: "AAA", name: "test" }); }, "must have a known type"); 14 | test.done(); 15 | }; 16 | 17 | exports["encode SUBMIT_JOB"] = function (test) { 18 | // examples 19 | // \0REQ-SUBMIT_JOB-6-test\0-\0/ 20 | var empty = new Buffer([0,0x52,0x45,0x51,0,0,0,0x7,0,0,0,0x6,0x74,0x65,0x73,0x74,0,0]), 21 | // \0REQ-SUBMIT_JOB-7-test\0-\0-a/ 22 | data = new Buffer([0,0x52,0x45,0x51,0,0,0,0x7,0,0,0,0x7,0x74,0x65,0x73,0x74,0,0,0x61]); 23 | 24 | test.throws(function () { packet.encode({ type: "SUBMIT_JOB" }); }, "job must have a name string"); 25 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB", name: "test" }), empty); 26 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB", name: "test", data: "a", encoding: "utf8" }), data); 27 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB", name: "test", data: new Buffer([0x61]) }), data); 28 | test.done(); 29 | }; 30 | 31 | exports["encode SUBMIT_JOB_HIGH"] = function (test) { 32 | // \0REQ-SUBMIT_JOB_HIGH-7-test\0-\0-a/ 33 | var data = new Buffer([0,0x52,0x45,0x51,0,0,0,21,0,0,0,7,0x74,0x65,0x73,0x74,0,0,0x61]); 34 | 35 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB_HIGH", name: "test", data: "a", encoding: "utf8" }), data); 36 | test.done(); 37 | }; 38 | 39 | exports["encode SUBMIT_JOB_LOW"] = function (test) { 40 | // \0REQ-SUBMIT_JOB_LOW-7-test\0-\0-a/ 41 | var data = new Buffer([0,0x52,0x45,0x51,0,0,0,33,0,0,0,7,0x74,0x65,0x73,0x74,0,0,0x61]); 42 | 43 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB_LOW", name: "test", data: "a", encoding: "utf8" }), data); 44 | test.done(); 45 | }; 46 | 47 | exports["encode SUBMIT_JOB_BG"] = function (test) { 48 | // \0REQ-SUBMIT_JOB_BG-7-test\0-\0-a/ 49 | var data = new Buffer([0,0x52,0x45,0x51,0,0,0,18,0,0,0,7,0x74,0x65,0x73,0x74,0,0,0x61]); 50 | 51 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB_BG", name: "test", data: "a", encoding: "utf8" }), data); 52 | test.done(); 53 | }; 54 | 55 | exports["encode SUBMIT_JOB_HIGH_BG"] = function (test) { 56 | // \0REQ-SUBMIT_JOB_HIGH_BG-7-test\0-\0-a/ 57 | var data = new Buffer([0,0x52,0x45,0x51,0,0,0,32,0,0,0,7,0x74,0x65,0x73,0x74,0,0,0x61]); 58 | 59 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB_HIGH_BG", name: "test", data: "a", encoding: "utf8" }), data); 60 | test.done(); 61 | }; 62 | 63 | exports["encode SUBMIT_JOB_LOW_BG"] = function (test) { 64 | // \0REQ-SUBMIT_JOB_LOW_BG-7-test\0-\0-a/ 65 | var data = new Buffer([0,0x52,0x45,0x51,0,0,0,34,0,0,0,7,0x74,0x65,0x73,0x74,0,0,0x61]); 66 | 67 | test.deepEqual(packet.encode({ type: "SUBMIT_JOB_LOW_BG", name: "test", data: "a", encoding: "utf8" }), data); 68 | test.done(); 69 | }; 70 | 71 | exports["encode GET_STATUS"] = function (test) { 72 | // \0REQ-GET_STATUS-5-test\0/ 73 | var data = new Buffer([0,0x52,0x45,0x51,0,0,0,15,0,0,0,4,0x74,0x65,0x73,0x74]); 74 | 75 | test.deepEqual(packet.encode({ type: "GET_STATUS", handle: "test" }), data); 76 | test.done(); 77 | }; 78 | 79 | exports["decode"] = function (test) { 80 | // \0RES 81 | var headerOnly = new Buffer([0, 0x52, 0x45, 0x53]), 82 | // \0RES-8 83 | withType = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 0x08]); 84 | // \0RES-8-0 85 | withTypeAndSize = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 0x08, 0, 0, 0, 0]); 86 | test.ok(typeof packet.decode === "function", "is a function"); 87 | test.ok(typeof packet.decode(withTypeAndSize) === "object", "returns an object"); 88 | test.throws(function () { packet.decode(); }, "input must be a Buffer"); 89 | test.throws(function () { packet.decode(new Buffer(0)); }, "must have a valid header"); 90 | test.throws(function () { packet.decode(headerOnly); }, "must have a valid type"); 91 | test.throws(function () { packet.decode(withType); }, "must have a valid packet length"); 92 | test.deepEqual(packet.decode(withTypeAndSize), { type: "JOB_CREATED", handle: "" }, "most basic request"); 93 | test.done(); 94 | }; 95 | 96 | exports["decode JOB_CREATED"] = function (test) { 97 | // \0RES-8-4-test 98 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 0x08, 0, 0, 0, 0x04, 0x74,0x65,0x73,0x74]); 99 | test.deepEqual(packet.decode(t), { type: "JOB_CREATED", handle: "test" }, "job created, handle 'test'"); 100 | test.done(); 101 | }; 102 | 103 | exports["decode WORK_COMPLETE"] = function (test) { 104 | // \0RES-13-12-test-\0-test 105 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 13, 0, 0, 0, 0x0c, 0x74,0x65,0x73,0x74,0,0x74,0x65,0x73,0x74]); 106 | test.deepEqual(packet.decode(t), { type: "WORK_COMPLETE", handle: "test", data: new Buffer([0x74, 0x65, 0x73, 0x74]) }, "work complete, handle 'test', data buffer"); 107 | test.done(); 108 | }; 109 | 110 | exports["decode WORK_FAIL"] = function (test) { 111 | // \0RES-14-4-test 112 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 14, 0, 0, 0, 4, 0x74,0x65,0x73,0x74]); 113 | test.deepEqual(packet.decode(t), { type: "WORK_FAIL", handle: "test" }, "work fail, handle 'test'"); 114 | test.done(); 115 | }; 116 | 117 | exports["decode WORK_EXCEPTION"] = function (test) { 118 | // \0RES-25-12-test-\0-test 119 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 25, 0, 0, 0, 0x0c, 0x74,0x65,0x73,0x74,0,0x74,0x65,0x73,0x74]); 120 | test.deepEqual(packet.decode(t), { type: "WORK_EXCEPTION", handle: "test", data: new Buffer([0x74, 0x65, 0x73, 0x74]) }, "work exception, handle 'test', data buffer"); 121 | test.done(); 122 | }; 123 | 124 | exports["decode WORK_DATA"] = function (test) { 125 | // \0RES-28-12-test-\0-test 126 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 28, 0, 0, 0, 0x0c, 0x74,0x65,0x73,0x74,0,0x74,0x65,0x73,0x74]); 127 | test.deepEqual(packet.decode(t), { type: "WORK_DATA", handle: "test", data: new Buffer([0x74, 0x65, 0x73, 0x74]) }, "work data, handle 'test', data buffer"); 128 | test.done(); 129 | }; 130 | 131 | exports["decode WORK_WARNING"] = function (test) { 132 | // \0RES-29-12-test-\0-test 133 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 29, 0, 0, 0, 0x0c, 0x74,0x65,0x73,0x74,0,0x74,0x65,0x73,0x74]); 134 | test.deepEqual(packet.decode(t), { type: "WORK_WARNING", handle: "test", data: new Buffer([0x74, 0x65, 0x73, 0x74]) }, "work warning, handle 'test', data buffer"); 135 | test.done(); 136 | }; 137 | 138 | exports["decode STATUS_RES"] = function (test) { 139 | // \0RES-STATUS_RES-12-test-\0-1-\0-1-\0-50-\0-100 140 | var t = new Buffer([0, 0x52, 0x45, 0x53, 0, 0, 0, 20, 0, 0, 0, 12, 0x74, 0x65, 0x73, 0x74, 0, 1, 0, 1, 0, 50, 0, 100]); 141 | test.deepEqual(packet.decode(t), { 142 | type: "STATUS_RES", 143 | handle: "test", 144 | known: true, 145 | running: true, 146 | percentComplete: [50, 100] 147 | }, "status response"); 148 | test.done(); 149 | }; 150 | --------------------------------------------------------------------------------