Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

making go http client work with non-standard http servers

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?

like image 791
Rhythmic Fistman Avatar asked Sep 26 '22 08:09

Rhythmic Fistman


1 Answers

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)
}
like image 99
captncraig Avatar answered Nov 15 '22 09:11

captncraig