5

Shoutcast servers basically speak http, with one important difference: they respond to GET requests with ICY 200 OK instead of HTTP/1.1 200 OK.

Go won't have a bar of it, and correctly fails with the error malformed HTTP version "ICY".

However I would like to make things work and am wondering what the best approach is. My ideas so far:

  1. use a custom http.Transport.Proxy to change ICY to HTTP/1.1 in-flight
  2. an out of process proxy that does the same thing
  3. overload http.ParseHTTPVersion (but golang doesn't have function overloading)
  4. duplicate the entire http package, just to modify ParseHTTPVersion

Number 1. seems the most attractive attractive, but I have no idea how to respect the http "scope" and actually modify all responses on a given http version. Is this the kind of thing http.Transport.Proxy can handle?

Can anyone give me any pointers?

Rhythmic Fistman
  • 30,464
  • 5
  • 74
  • 138
  • 1
    Who's idea was it to deviate from the spec in such a fundamental way? It just seems like a bad idea. Have you considered forking out the net/http package and making it less strict? – captncraig Nov 02 '15 at 06:27
  • 4
    That's wrong. Icecast does NOT reply like that. It's a HTTP 1.0 compliant server. Shoutcast on the other hand is being retarded and not a HTTP server. – TBR Nov 02 '15 at 06:39
  • 1
    @captncraig 1997 was a simpler time... Not that it excuses it. – Brad Nov 02 '15 at 18:26
  • Is there something public that speaks this I can test against? – captncraig Nov 02 '15 at 18:37
  • If they purposely broke the HTTP response, there might be a reason. I wouldn't be surprised if there's other pieces of their protocol that diverge from HTTP. – JimB Nov 02 '15 at 20:51
  • @TBR thanks, I'd been conflating shoutcast and icecast, and have edited icecast out. – Rhythmic Fistman Nov 09 '15 at 01:17
  • @JimB I agree, I _should_ implement the icecast protocol myself (along with basic auth, headers, etc), but don't have time now, so I'm going with captncraig's dial solution, even though it will only work once per connection. – Rhythmic Fistman Nov 09 '15 at 01:18

1 Answers1

3

I got this working by creating a custom Dial function that returns a wrapped connection. My wrapper intercepts the first read on the connection and replaces ICY with HTTP/1.1. Not super robust, but proves the concept:

package main

import (
    "fmt"
    "net"
    "net/http"
)

type IcyConnWrapper struct {
    net.Conn
    haveReadAny bool
}

func (i *IcyConnWrapper) Read(b []byte) (int, error) {
    if i.haveReadAny {
        return i.Conn.Read(b)
    }
    i.haveReadAny = true
    //bounds checking ommitted. There are a few ways this can go wrong.
    //always check array sizes and returned n.
    n, err := i.Conn.Read(b[:3])
    if err != nil {
        return n, err
    }
    if string(b[:3]) == "ICY" {
        //write Correct http response into buffer
        copy(b, []byte("HTTP/1.1"))
        return 8, nil
    }
    return n, nil
}

func main() {

    tr := &http.Transport{
        Dial: func(network, a string) (net.Conn, error) {
            realConn, err := net.Dial(network, a)
            if err != nil {
                return nil, err
            }
            return &IcyConnWrapper{Conn: realConn}, nil
        },
    }
    client := &http.Client{Transport: tr}
    http.DefaultClient = client
    resp, err := http.Get("http://178.33.230.189:8100") //random url I found on the internet
    fmt.Println(err)
    fmt.Println(resp.StatusCode)
}
captncraig
  • 19,430
  • 11
  • 95
  • 139
  • I'd recommend to indicate a lower HTTP protocol version. I seem to remember that Shoutcast is a variation on HTTP 0.9. So that or 1.0 might avoid problems on the client side. – TBR Nov 04 '15 at 07:40
  • Probably a good idea, but I doubt a 1.0 compliant response would have anything unparsable to a 1.1 client. If it is actually 1.0 compliant is another question. – captncraig Nov 04 '15 at 15:31
  • This won't work for connection reuse, but this gets me over the line, and who am I kidding, it'll probably be in production for years before I get around to properly implementing icecast. Thank you! – Rhythmic Fistman Nov 09 '15 at 01:20