Split handling logic into separate classes

Rather than having all the logic lumped into a single ChannelHandler,
split it across multiple ChannelHandlers. Each of these smaller
ChannelHandlers has a single responsibility, for example checking for
bad requests, handling WebSocket upgrades, etc.