Here are the steps for installing Apache2 with SSL support on your local development machine. Several of our applications require HTTPS for certain actions and I’ve been bitten in the past by not testing this in development. So, here’s how to get Apache up and running with SSL.
Compiling and Installing Apache
- Download the latest source distribution (v2.2.15 for me) from: http://httpd.apache.org
- Extract the source:
tar -xvf httpd-2.2.15.tar.gz
- Create an install directory (I put it in
/usr/local): sudo makedir /usr/local/apache2
- Configure the makefile with the following options:
./configure --prefix=/usr/local/apache2 --enable-ssl --enable-setenvif --enable-proxy --enable-headers
- Compile the source code:
make
- Install apache:
sudo make install
- Create your self-signed SSL keys by following this tutorial: http://developer.apple.com/internet/serverside/modssl.html
- Scroll down to the Configuring SSL section and start from there
- You will need to download mod_ssl to get the sign.sh script mentioned in the tutorial but that’s all you need it for. You’ll notice the mod_ssl site says it’s only for Apache 1.3.X. That is because Apache2 has built in SSL support which we enabled in the makefile configuration above so mod_ssl is no longer needed.
- Edit your httpd.conf file:
sudo vi /usr/local/apache2/conf/httpd.conf
- Add the following configuration to the bottom of the file to proxy all HTTP and HTTPS requests to port 3000, i.e., Rails:
- This assumes Rails is running on port 3000 on your machine
- This assumes you put the SSL keys where the tutorial told you to
- The last tag might already be in your
httpd.conf file, if so, no need to repeat it here
# Apache needs to know you want to accept connections over HTTPS
Listen 443
SSLCertificateFile /etc/httpd/ssl.key/server.crt
SSLCertificateKeyFile /etc/httpd/ssl.key/server.key
# Below is optional, but was helpful to me in debugging this setup
CustomLog logs/ssl_request_log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<VirtualHost *:80>
ServerName localhost
ServerAlias 127.0.0.1
ProxyPass / <a href="http://localhost:3000/">http://localhost:3000/</a>
ProxyPassReverse / <a href="http://localhost:3000">http://localhost:3000</a>
ProxyPreserveHost on
<VirtualHost *:443>
SSLEngine On
ServerName localhost
ServerAlias 127.0.0.1
ProxyPass / <a href="http://localhost:3000/">http://localhost:3000/</a>
ProxyPassReverse / <a href="http://localhost:3000">http://localhost:3000</a>
ProxyPreserveHost on
RequestHeader set X_FORWARDED_PROTO 'https'
<ifmodule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</ifmodule>
Auto-starting Apache
If you’d like to have apache start automatically whenever your computer starts do the following:
- Create a new plist file for apache with a unique name:
sudo vi /Library/LaunchDaemons/org.apache.httpd
- Put the following in the file (assuming you installed apache in
/usr/local/apache2):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<true />
<key>Label</key>
<string>org.apache.httpd</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/apache2/bin/apachectl</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true />
<key>UserName</key>
<string>root</string>
<key>WorkingDirectory</key>
<string>/usr/local/apache2</string>
</dict>
</plist>
- Test it out with launchd:
sudo launchctl load -w /Library/LaunchDaemons/org.apache.httpd; You should see several instances of httpd running if you do a: ps aux | grep httpd
Testing it out
- Fire up Rails on whatever port you are forwarding your HTTP/HTTPS requests to
- Request an action with HTTP
- Request an action with HTTPS
Possible Problems
I encountered the following cryptic error in FireFox when I was testing out an Apache install on a second dev box:
SEC_ERROR_REUSED_ISSUER_AND_SERIAL
I found out by reading through some forums that each SSL certificate needs to have a unique serial number. Since I had followed the above procedure to install Apache and SSL on both my laptop and the second dev box I already had a certificate with the same serial number stored in my browser (for my local machine) as the one on the second dev box. If you follow the above process the serial number of your certificate will be 01. So to fix this do the following on your new certificate (for me it was the one on the dev box):
sudo openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out server.crt
This simply assigns 02 to the second certificate and keeps FireFox happy. You probably will not encounter this problem unless you are testing SSL on multiple machines like I was but I figured I’d mention it anyways. Note, you can see the serial number of a certificate in FireFox by going to:
Firefox > Preferences > Advanced > View Certificates (button)
Then double click on a certificate of interest. Also note that Chrome did not complain about this but FireFox won’t even let you visit a site with a duplicate serial number.
If you get stuck…
Try one of the following resources that I used to get this up and running:
Ever have Expose freeze up on you? Command-TAB stops working, Spaces stops working, the Dock stops working, show desktop stops working, show windows stops working; you’re trapped! Well, if you happen to have a terminal window open or can launch one, fear not! Simply issue the following command (make sure to use a capital ‘D’) and you’re golden. No restart required!
$ killall Dock
Share and enjoy!
Recently I had a problem with Bj where it would not start up. Nothing was written to the backgroundjob log or Rails log and no exception was being thrown. To make the problem even stranger, Bj would start-up just fine in development but not in production but worked just fine in production from script/console. After digging into the Bj code and adding some debug statements I found the problem.
# database.yml
development:
adapter: mysql
database: my_development
username: me
password: password
host: localhost
port: 3306
test:
adapter: mysql
database: my_test
username: me
password: password
host: localhost
port: 3306
production:
development
Bj was getting an ActiveRecord::ConnectionNotEstablished exception but was swallowing it. The solution was to explicitly define the production database connection in database.yml.
I recently found the need to provide UUIDs for ActiveRecord models in a service that I’m developing. I wasn’t able to find a suitable soution, so, I rolled my own. Enter acts_as_universally_unique. The plugin simply adds a (customizabe) UUID field to all ActiveRecord models that act_as_universally_unique. I will be adding additional methods (and test cases) to it shortly.
According to the XML-RPC specification a XML_RPC request may only contain scalar <value>s or non-scalar <struct>s. The specification unfortunately does not provide any standard for encoding the type of data encoded in the <struct>s. This has the side effect of not being able to support polymorphism in service method parameters as it leaves the sever no choice but to rely on the method signature in the API declaration when trying to determine what to instantiate for a given <struct> in the XML-RPC request.
Let’s say you have the following declarations:
class SubscriptionsApi < ActionWebService::API::Base
api_method(
:create_subscription,
:expects => [
{ :customer => Logical::Customer },
{ :payment_method => Logical::PaymentMethod }
]
)
end
module Logical
class PaymentMethod < ActionWebService::Struct
end
class CreditCard < PaymentMethod
member :card_number, :string
# ...
end
class PayPal < PaymentMethod
member :login, :string
# ...
end
end
Now you want to make a call to the service method and pass either a CreditCard or a PayPal. XML-RPC will encode the request like so:
<methodCall>
<methodName>create_subscription</methodName>
<param>
<struct>
<member>
<name>card_number</name>
<value>4111-1111-1111-1111</value>
</member>
</struct>
</param>
</methodCall>
This provides no type information to the server so the server will attempt to instantiate a Logical::PaymentMethod which will of course not have a card_number member as it’s specific to the CreditCard subclass. SOAP, on the other hand, does encode the parameter types allowing you to utilize this type of polymorphism in your service parameters. Here’s the same request encoded in SOAP.
<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<n1:CreateSubscription xmlns:n1="urn:ActionWebService" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<payment_method xmlns:n2="http://www.ruby-lang.org/xmlns/ruby/type/custom" xsi:type="n2:Logical..CreditCard">
<card_number xsi:type="xsd:string">1</card_number>
</payment_method>
</n1:CreateSubscription>
</env:Body>
</env:Envelope>
The current implementation of ActionWebService resurrected by datanoise did not support this type of polymorphism in SOAP requests. However, I submitted a patch recently which provides for this functionality. Hopefully it’s accepted.
I recently came across an annoying bug in MySQL v5.1 (also in 6.0 apparently) that bit me hard, so, I thought I’d post on it in case you are being bitten by the same bug.
If you attempt to update a column that does not allow NULL to NULL, MySQL will set the column’s value to the default value for that column’s data type. This is true only when you are not running MySQL strict mode. Here’s an example to illustrate.
mysql> create table null_test (id int not null unique(id), name varchar(25)
null default null);
Query OK, 0 rows affected (0.01 sec)
mysql> show create table null_test;
+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| null_test | CREATE TABLE `null_test` (
`id` int(11) NOT NULL,
`name` varchar(25) default NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> insert into null_test (id, name) values (1, 'Jane');
Query OK, 1 row affected (0.00 sec)
mysql> update null_test set id = null;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> select * from null_test where name = 'Jane';
+----+------+
| id | name |
+----+------+
| 0 | Jane |
+----+------+
1 row in set (0.00 sec)
More information about this bug can be found in the bug report submitted Janurary 4, 2008.
Your RDBMS will usually rewrite your sub-selects behind the scenes as joins. However, there are times where you’ll want to do this yourself. For example, past versions of MySQL did not play well with sub-selects. Here are a couple examples of how to rewrite sub-selects as joins.
select *
from table_a
where id not in (select a_id from table_b);
-- can be rewritten as...
select *
from table_a
left outer join table_b on table_a.id = table_b.a_id
where table_b.a_id is null;
select *
from table_a
where id in (select a_id from table_b);
-- can be rewritten as...
select *
from table_a
left outer join table_b on table_a.id = table_b.a_id
where table_b.a_id is not null;