From 9ba8adf164b316a3d6914b3b187f371f6e19066d Mon Sep 17 00:00:00 2001 From: James Maki Date: Mon, 10 May 2010 14:26:36 -0500 Subject: Add sms web service example --- contrib/smsweb/smswsc.rb | 341 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100755 contrib/smsweb/smswsc.rb (limited to 'contrib/smsweb/smswsc.rb') diff --git a/contrib/smsweb/smswsc.rb b/contrib/smsweb/smswsc.rb new file mode 100755 index 0000000..1800e13 --- /dev/null +++ b/contrib/smsweb/smswsc.rb @@ -0,0 +1,341 @@ +#!/usr/bin/env ruby1.8 +# +# vim: set sw=2 ts=2 expandtab: +# +# SMS Web Service client example library +# +# Copyright (C) 2010 by James Maki +# +# Author: James Maki +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +require 'rubygems' +require 'net/http' +require 'cgi' +require 'inform' +require 'smswsc/utils' +require 'smswsc/phonebook_entry' +require 'smswsc/sms_message' + +module SMSWSC + module Error + class General < RuntimeError; end + class HTTP < General; end + class XML < General; end + class BadRequest < General; end + class Unauthorized < General; end + class Forbidden < General; end + class NotFound < General; end + class ServiceUnavailable < General; end + end + + SMSWS_PATH = "/smsws/v1" + PHONEBOOK_PATH = Utils.ref(SMSWS_PATH, "phonebook") + MESSAGES_PATH = Utils.ref(SMSWS_PATH, "messages") + SEND_PATH = Utils.ref(SMSWS_PATH, "send") + + class Client + include Utils + + DEFAULT_PORT = 5857 + VERSION = '0.1' + + attr_accessor :use_ssl + attr_accessor :http_debug + attr_accessor :http_read_timeout + + def initialize(username, passwd, host, port = DEFAULT_PORT) + @host = host + @port = port + @username = username + @passwd = passwd + @error = "" + @headers = { + 'Content-Type' => 'application/xml', + 'User-Agent' => "smswsc/#{VERSION}", + } + @use_ssl = false + @http_debug = false + @http_read_timeout = 300 + end + + def user_agent(ua) + @headers['User-Agent'] = ua + end + + def phonebook_query(path = PHONEBOOK_PATH) + case path + when PhonebookEntry + path = path.uri.to_s + when String + when URI + path = path.to_s + else + raise ArgumentError, "Invalid param type: #{path.class}" + end + + phonebook = [] + + response = get_resource(path) + + root = process_response(response, Net::HTTPOK) + + root.elements.each("phonebook-entry") do |entry| + phonebook << PhonebookEntry.load_xml(entry) + end + + return phonebook + end + + def sms_messages_query(path = MESSAGES_PATH) + case path + when SMSMessage + path = path.uri.to_s + when String + when URI + path = path.to_s + else + raise ArgumentError, "Invalid param type: #{path.class}" + end + + msgs = [] + + response = get_resource(path) + + root = process_response(response, Net::HTTPOK) + + root.elements.each("sms-message") do |entry| + msgs << SMSMessage.load_xml(entry) + end + + return msgs + end + + def sms_messages_delete(path = MESSAGES_PATH) + case path + when SMSMessage + path = path.uri.to_s + when String + when URI + path = path.to_s + else + raise ArgumentError, "Invalid param type: #{path.class}" + end + + response = delete_resource(path) + + root = process_response(response, Net::HTTPOK) + + return true + end + + def sms_send(msg) + response = post_resource(SEND_PATH, msg.to_xml) + + root = process_response(response, Net::HTTPCreated) + + return true + end + + private + + def process_response(response, klass) + root = response_xml(response) + if root + message = root.elements['status-message'].text + else + message = "response message not given" + end + + unless response.instance_of?(klass) + if response.instance_of?(Net::HTTPBadRequest) + raise Error::BadRequest, "Bad Request: " + message + elsif response.instance_of?(Net::HTTPUnauthorized) + raise Error::Unauthorized, "Unauthorized: " + message + elsif response.instance_of?(Net::HTTPForbidden) + raise Error::Forbidden, "Forbidden: " + message + elsif response.instance_of?(Net::HTTPNotFound) + raise Error::NotFound, "Not Found: " + message + elsif response.instance_of?(Net::HTTPServiceUnavailable) + raise Error::ServiceUnavailable, "Service Unavailable: " + message + else + raise Error::General, "Failed to fulfill request: " + message + end + end + + return root + end + + def response_xml(response) + return nil unless response.content_type.to_s.downcase == 'application/xml' + + begin + document = REXML::Document.new(response.body) + root = document.root + rescue REXML::ParseException => e + Inform.err(e.to_s) + raise Error::XML, "failed to parse response XML" + end + + return root + end + + def authorize(request) + if @username && @passwd + request.basic_auth(@username, @passwd) + end + end + + def parse_qpath(url) + url = URI.parse(url) + qpath = url.path + if url.query + qpath += "?" + url.query + end + + return qpath + end + + def net_http + http = Net::HTTP.new(@host, @port) + + http.read_timeout = @http_read_timeout + http.set_debug_output($stderr) if @http_debug + + if @use_ssl + http.use_ssl = @use_ssl + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + return http + end + + def post_resource(ref, body, headers = {}) + headers = @headers.merge(headers) + qpath = parse_qpath(ref) + + begin + request = Net::HTTP::Post.new(qpath, headers) + authorize(request) + + response = net_http.start do |http| + http.request(request, body) + end + rescue => e + Inform.err("POST #{ref} failed: #{e.class}: #{e}") + raise Error::HTTP, "POST #{ref} failed: #{e}" + end + + return response + end + + def delete_resource(ref, headers = {}) + headers = @headers.merge(headers) + qpath = parse_qpath(ref) + + begin + request = Net::HTTP::Delete.new(qpath, headers) + authorize(request) + + response = net_http.start do |http| + http.request(request) + end + rescue => e + Inform.err("DELETE #{ref} failed: #{e.class}: #{e}") + raise Error::HTTP, "DELETE #{ref} failed: #{e}" + end + + return response + end + + def get_resource(ref, headers = {}) + headers = @headers.merge(headers) + qpath = parse_qpath(ref) + + begin + request = Net::HTTP::Get.new(qpath, headers) + authorize(request) + + response = net_http.start do |http| + http.request(request) + end + rescue => e + Inform.err("GET #{ref} failed: #{e.class}: #{e}") + raise Error::HTTP, "GET #{ref} failed: #{e}" + end + + return response + end + + def put_resource(ref, body, headers = {}) + headers = @headers.merge(headers) + qpath = parse_qpath(ref) + + begin + request = Net::HTTP::Put.new(qpath, headers) + authorize(request) + + response = net_http.start do |http| + http.request(request, body) + end + rescue => e + Inform.err("PUT #{ref} failed: #{e.class}: #{e}") + raise Error::HTTP, "PUT #{ref} failed: #{e}" + end + + return response + end + end +end + +if $0 == __FILE__ then + SMSWS_ROOT = File.expand_path(File.dirname(__FILE__)) + + Dir.chdir(SMSWS_ROOT) + + SMSWS_CONFIG = "#{SMSWS_ROOT}/sms.config" + $config = YAML::load_file(SMSWS_CONFIG) + + ENV['SMS_CONFIG'] = "#{SMSWS_CONFIG}" + + Inform.syslog = false + if Inform.syslog + Syslog.open("smswsc", Syslog::LOG_NDELAY, Syslog::LOG_LOCAL7) + else + Inform.level = Inform::LOG_ERR + #Inform.level = Inform::LOG_DEBUG + Inform.file = $stdout + end + + client = SMSWSC::Client.new( + $config["smsws"]["user"], + $config["smsws"]["passwd"], + "192.168.2.1", + $config["smsws"]["port"] + ) + + msgs = client.sms_messages_query + p msgs + + pb = client.phonebook_query + p pb + + msg = SMSWSC::SMSMessage.new + msg.addr = "jcm" + msg.user_data = "test me web by name" + client.sms_send(msg) +end -- cgit v1.2.3