Lei Wu, Web Developer

Keeping simple things simple.

Maintenance Page for .NET Applications Using app_offline.htm

The easiest way to create a maintenance page for a .Net application is creating a static html page called app_offline.htm and saving it to the root directory.

When IIS detects the existence of app_offline.htm, it will not only display the maintenance page, but also return a “503 Service Temporarily Unavailable” error code, which is important to prevent search engine crawlers from indexing the maintenance message as your site’s content.

A nice little trick: You could use data uri to embed images in your app_offline.htm, such as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Maintenance</title>
    <style type="text/css">
        #logo
        {
            width: 216px;
            height: 105px;
            background-repeat: no-repeat;
            background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANgAAABpCAYAAABYtu09AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA7dpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6Rjk3RjExNzQwNzIwNjgxMTgwODNFQjgzQzYyQkQ3QzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODNCNzQ0RkFDRkMxMTFFMjg5NzU4NzFDODk4Q0JENUMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODNCNzQ0RjlDRkMxMTFFMjg5NzU4NzFDODk4Q0JENUMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjc5QUQ2N0E3MTcyMDY4MTE4MDgzQzJDQzYxNjRF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);
        }
    </style>
</head>
<body>
    <div id="logo"></div>
    <div lang="en">Here goes the maintenance message.</div>
</body>
</html>

Failed Attempt to Upgrade Lucene.Net for Kentico 7

A web site I’m working on uses Kentico CMS version 7. Occasionally its search function would break down due to a corrupted Lucene index.

Kentico CMS’s SmartSearch is built on top of Lucene.Net. Kentico 7 comes with Lucene.Net 2.1.0.3, a very old version. One of the things I tried was to upgrade Lucene.Net.

Although the latest version is 3.0.3, I decided to go with 2.9.4.1.

nuget install Lucene.Net -Version 2.9.4.1

Then I replaced Kentico’s Lucene.Net.dll and dependency ICSharpCode.SharpZipLib.dll.

As expected, Kentico throws an error “Could not load file or assembly ‘Lucene.Net, Version=2.1.0.3, Culture=neutral, PublicKeyToken=d846ee227f01c7a1’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference.”

Trying to work around it, I added the following to web.config:

1
2
3
4
5
6
7
8
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
          <assemblyIdentity name="Lucene.Net" publicKeyToken="d846ee227f01c7a1"/>
          <bindingRedirect oldVersion="2.1.0.3" newVersion="2.9.4.1"/>
      </dependentAssembly>
      </assemblyBinding>
   </runtime>

Unfortunately, it didn’t help. A closer look at the error message shows the exception was thrown by CMS.LicenseProvider.LicenseHelper.

Logging Real Client IPs in IIS 7

If your IIS 7 server sits behind a load balancer or a proxy server, it will always record the IP of the proxy or load balancer as the client IP, which makes it impossible to determine the origin of your visitors.

The solution is to install an ISAPI to capture the X-Forwarded-For header.

Load Test With Gatling

Recently I discovered Gatling, a load testing framework written in Scala, and used it to test a Kentico 7 web site. I love it so much that I’ll ditch Apache JMeter from now on.

While both have a GUI to record tests, the major difference between them is JMeter tests are saved as .jmx files (XML), but Gatling tests are .scala scripts written in Scala-based DSL.

Another interesting option would be Ruby JMeter, but I haven’t had a chance to try it yet.

This blog from flood.io points out that Gatling outperformances JMeter with over 20,000 concurrent users.

I must admit, however, that Gatling stil has a few rough edges, at least on Windows. For example, some of my tests couldn’t finish because a report file’s file name was too long for Windows (260 characters maximum).

Additionally, Gatling’s QuickStart documentation warns:

  • We ask you to not use a path containing spaces, as this can cause issues.
  • For Windows users, we also recommend that you do not place Gatling in the Programs folder as there may be permission issues.

Blogging With GitHub, Octopress and Cloud 9

This blog is hosted by GitHub Pages and built with Octopress.

I’m using Cloud 9 as a web-based IDE.

As a Ubuntu user and a Ruby enthusiast who prefers simple solutions, they’re a perfect combo.

I was just about to blog on how to set them up, when I found out this guy’s post beat me by a year. :-) It’s a great post with step by step instructions.

I have just the following to add:

  1. To set up your SSH key authentication, you can locate you Cloud 9 SSH key by going to “Your Account”, and then “Show SSH Key” under “Account Settings” on the right hand side. Copy that key, click the Settings icon on the top right corner of your GitHub page, and then “SSH Keys” on the left side. Click “Add SSH Key” on the right to add your Cloud 9 SSH Key.

  2. In case you use non-ASCII characters, for example, if you’re writing in German, you may get the followoing error when you run rake generate:

YAML Exception reading 2014-06-11-learn-german.markdown: invalid byte sequence in US-ASCII
/home/ubuntu/workspace/octopress/plugins/backtick_code_block.rb:13:in `gsub': invalid byte sequence in US-ASCII (ArgumentError)
        from /home/ubuntu/workspace/octopress/plugins/backtick_code_block.rb:13:in `render_code_block'
        from /home/ubuntu/workspace/octopress/plugins/octopress_filters.rb:12:in `pre_filter'
        from /home/ubuntu/workspace/octopress/plugins/octopress_filters.rb:28:in `pre_render'
        from /home/ubuntu/workspace/octopress/plugins/post_filters.rb:112:in `block in pre_render'
        from /home/ubuntu/workspace/octopress/plugins/post_filters.rb:111:in `each'
        from /home/ubuntu/workspace/octopress/plugins/post_filters.rb:111:in `pre_render'
        from /home/ubuntu/workspace/octopress/plugins/post_filters.rb:166:in `do_layout'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/gems/jekyll-0.12.0/lib/jekyll/post.rb:195:in `render'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/gems/jekyll-0.12.0/lib/jekyll/site.rb:200:in `block in render'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/gems/jekyll-0.12.0/lib/jekyll/site.rb:199:in `each'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/gems/jekyll-0.12.0/lib/jekyll/site.rb:199:in `render'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/gems/jekyll-0.12.0/lib/jekyll/site.rb:41:in `process'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/gems/jekyll-0.12.0/bin/jekyll:264:in `<top (required)>'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/bin/jekyll:23:in `load'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/bin/jekyll:23:in `<main>'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/bin/ruby_executable_hooks:15:in `eval'
        from /usr/local/rvm/gems/ruby-2.1.1@rails4/bin/ruby_executable_hooks:15:in `<main>'

The solution is run export LC_ALL=C.UTF-8 first. To save time, I would join the commands as export LC_ALL=C.UTF-8 && rake generate.

(Some posts on the web suggest expoort LC_ALL=en_US.UTF-8, but that won’t work for the current version Cloud 9 at the time of writing. To be sure, you can run locale -a to get a list of available locales.)

Sinatra, JBoss 6, JDBC and ActiveRecord

The article describes how to create a JRuby Sinatra web application and deploy it on JBoss 6.

It covers these key points:

  • ActiveRecord (including validation)
  • JDBC (including JBoss connection pool)
  • SQL Server
  • Form Helpers
  • tux
  • Warbler

We also use a little bit of Twitter Bootstrap for our views.

The source code of the application is available on GitHub at https://github.com/wuleicanada/myapp .

Set up dev environment on Windows 7

Install JRuby

For Windows 7, just download and install the exe(x64) executable.

Create the App

Let’s create a folder with the same name of our JBoss application context.

md myapp

cd myapp

Install necessary gems

First things first, install Bundler jruby --command gem install bundler.

Now we’ll create the Gemfile with the following content:

Gemfile
1
2
3
4
5
6
7
8
9
10
11
12
source "http://rubygems.org"

gem "sinatra"
gem "activerecord"
gem "sinatra-activerecord"
gem 'activerecord-jdbc-adapter'
gem 'sinatra-formhelpers-ng'


group :development do
  gem "tux"
end

And then run jruby --command bundle install to get the gems ready.

Note: The tux gem works as a console to interact with my Sinatra application. I would also love to have the shotgun gem, but unfortuantely it’s unavaible for JRuby.

The JDBC driver (sqljdbc4.jar) for SQL Server is included in the source code, but if you prefer, you can also get it from Microsoft’s web site. We’ll just save it to our application root.

SQL Server table

Our application will use one table called “users”.

1
2
3
4
5
6
7
8
9
10
CREATE TABLE [users](
  [id] [int] IDENTITY(1,1) NOT NULL,
  [firstname] [varchar](32) NULL,
  [lastname] [varchar](32) NULL,
  [email] [varchar](64) NULL,
 CONSTRAINT [PK_users] PRIMARY KEY CLUSTERED
(
  [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Let’s insert a user insert users (firstname, lastname, email) values ('John', 'Smith', 'john@smith.com')

Code the application

Our classic style Sinatra application consists of the following files:

config.ru

config.ru
1
2
require File.join(File.dirname(__FILE__), 'app')
run Sinatra::Application

app.rb

app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require 'rubygems'
require "sinatra"
require "sinatra/activerecord"
require 'sinatra/form_helpers'
require 'active_record'
require 'active_record/connection_adapters/jdbc_adapter'
require 'sqljdbc4.jar'


config_dev = {
  :adapter => "jdbc",
  :driver => "com.microsoft.sqlserver.jdbc.SQLServerDriver",
  :url => "jdbc:sqlserver://SERVER:1433;databaseName=DB_NAME",
  :username => "USER",
  :password => "PASS"
}

# JBoss connectoin pool. Need to set up datasource in JBoss
config_jboss = {
  :adapter => "jdbc",
  :driver => "com.microsoft.sqlserver.jdbc.SQLServerDriver",
  :jndi => "DATASOURCE_NAME"
}

# On JBoss $servlet_context is defined
config = defined?($servlet_context) ? config_jboss : config_dev
ActiveRecord::Base.establish_connection(config)

class User < ActiveRecord::Base
  validates :consent, acceptance: true, presence: true
  validates :firstname, presence: true
  validates :lastname, presence: true
  validates :email, presence: true, format: { with: /\A[^@]+@([^@\.]+\.)+[^@\.]+\z/ }
end


helpers do
  def h(text)
      Rack::Utils.escape_html(text)
  end
end


get "/users" do
  @users = User.take(10)
  erb :"users/index"
end


get "/users/:id" do
  @user = User.find(params[:id])
  erb :"users/show"
end

put "/users/:id" do
  @user = User.find(params[:id])
  if @user.update_attributes(params[:user])
      redirect '/users'
  else
      erb :"users/show"
  end
end

Our layouts and templates sit in the views folder with the following content.

layout.erb

layout.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<title>MyApp</title>
 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
 <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>    
</head>
<body>

<%= yield %>

</body>
</html>

users/index.erb

users/index.erb
1
<%= h @users %>

users/show.erb

users/show.erb
1
2
3
4
5
6
7
<%= form('/users/' + @user.id.to_s, :put) %>
 <div>First Name: <%= input(:user, :firstname, :value=>h(@user.firstname), :size=>20)%></div>
  <div>Last Name: <%= input(:user, :lastname, :value=>h(@user.lastname), :size=>20)%></div>
  <div>Email: <%= input(:user, :email, :value=>h(@user.email), :size=>40, :disabled=>:disabled)%></div>
  <div><%= checkbox(:user, :consent, '1', :label=> false) %></span> Yes! I accept the term of service.</div>
 <button type="submit" class="btn btn-default">Submit</button>
</form>

Start our dev server

Run jruby app.rb and now our wonderful little app will be available at http://localhost:4567/users/

Deploy to JBoss 6

Create WAR file using Warbler

Let’s get started by installing Warbler as a gem jruby --command gem install warbler. Next, we need to configure Warbler:

  • Create a new folder called config under our application root
  • Run jruby --command warble config, which generates a configuration template called warble.rb under the config folder
  • Edit config/warble.rb and update these lines:
config/warble.rb
1
2
3
4
5
6
7
8
    # Application directories to be included in the webapp.
    config.dirs = %w(views)

    # Additional files/directories to include, above those in config.dirs
    config.includes = FileList['app.rb', 'sqljdbc4.jar']

    # Uncomment this if you don't want to package rails gem.
    config.gems -= ["rails"]

Now we’re ready to package our app: jruby --command warble

A war file will be created as myapp.war.

Deploy the WAR file

I tried the JBoss 6 Admin console first, but got an Out of Memory error. I worked around it by copying the WAR file to the deploy folder (in my case /usr/jboss6/server/default/deploy/) and restarting JBoss. And voila, our application is live on JBoss at http://jboss_address/myapp .