├── .github └── workflows │ └── release.yml ├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── signaling.iml └── vcs.xml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── auto └── pub.sh ├── go.mod ├── go.sum ├── main.go ├── vendor ├── github.com │ └── ossrs │ │ └── go-oryx-lib │ │ ├── LICENSE │ │ ├── errors │ │ ├── LICENSE │ │ ├── README.md │ │ ├── errors.go │ │ └── stack.go │ │ └── logger │ │ ├── go17.go │ │ ├── logger.go │ │ └── pre_go17.go ├── golang.org │ └── x │ │ └── net │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── websocket │ │ ├── client.go │ │ ├── dial.go │ │ ├── hybi.go │ │ ├── server.go │ │ └── websocket.go └── modules.txt └── www ├── crossdomain.xml ├── demos ├── css │ └── bootstrap.min.css ├── img │ ├── shields-io-signaling.svg │ └── tooltip.png ├── index.html ├── js │ ├── adapter-7.4.0.js │ ├── adapter-7.4.0.min.js │ ├── jquery-1.12.2.min.js │ ├── jquery-1.12.2.min.map │ ├── srs.sdk.js │ └── srs.sig.js ├── one2one.html └── room.html ├── favicon.ico └── index.html /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release SRS demo signaling" 2 | 3 | on: 4 | push: 5 | tags: 6 | - v1* 7 | 8 | jobs: 9 | docker: 10 | name: release-docker 11 | runs-on: ubuntu-20.04 12 | 13 | steps: 14 | ################################################################ 15 | # Git checkout 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | 19 | # Generate variables like: 20 | # SRS_DROPLET_EIP=1.2.3.4 21 | - name: Build droplet variables 22 | run: | 23 | SRS_DROPLET_EIP=$(dig +short lh.ossrs.net) 24 | echo "SRS_DROPLET_EIP=$SRS_DROPLET_EIP" >> $GITHUB_ENV 25 | 26 | ################################################################ 27 | # Build 28 | # The github.ref is, for example, refs/tags/v1.0.52 29 | # Generate variables like: 30 | # SRS_TAG=v1.0.52 31 | # SRS_MAJOR=1 32 | # @see https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 33 | - name: Generate varaiables 34 | run: | 35 | SRS_TAG=$(echo ${{ github.ref }}| awk -F '/' '{print $3}') 36 | echo "SRS_TAG=$SRS_TAG" >> $GITHUB_ENV 37 | SRS_MAJOR=$(echo $SRS_TAG| cut -c 2) 38 | echo "SRS_MAJOR=$SRS_MAJOR" >> $GITHUB_ENV 39 | # Build SRS image 40 | - name: Build SRS docker image 41 | run: | 42 | echo "Release ossrs/signaling:$SRS_TAG" 43 | docker build --tag ossrs/signaling:$SRS_TAG . 44 | 45 | ################################################################ 46 | # Aliyun ACR 47 | - name: Login Aliyun docker hub 48 | uses: aliyun/acr-login@v1 49 | with: 50 | login-server: https://registry.cn-hangzhou.aliyuncs.com 51 | username: "${{ secrets.ACR_USERNAME }}" 52 | password: "${{ secrets.ACR_PASSWORD }}" 53 | - name: Push to Aliyun docker hub 54 | run: | 55 | docker tag ossrs/signaling:$SRS_TAG registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:$SRS_TAG 56 | docker tag ossrs/signaling:$SRS_TAG registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:$SRS_MAJOR 57 | docker push --all-tags registry.cn-hangzhou.aliyuncs.com/ossrs/signaling 58 | 59 | ################################################################ 60 | # Execute command in a ssh, because ufw limit the rate. 61 | - name: Restart the containers 62 | uses: appleboy/ssh-action@master 63 | with: 64 | host: ${{ env.SRS_DROPLET_EIP }} 65 | username: root 66 | key: ${{ secrets.DIGITALOCEAN_SSHKEY }} 67 | port: 22 68 | envs: SRS_TAG,SRS_MAJOR 69 | timeout: 60s 70 | command_timeout: 30m 71 | script: | 72 | # 73 | ufw allow 1989 # For SRS demo signaling service. 74 | # 75 | # Restart SRS signaling service 76 | cat << END > /root/restart_docs-signaling.sh 77 | # See https://github.com/ossrs/signaling 78 | docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:$SRS_MAJOR 79 | docker rm -f docs-signaling || sleep 1 80 | docker run -d -it --restart always --name docs-signaling -p 1989:1989 \\ 81 | --log-driver=json-file --log-opt=max-size=500m --log-opt=max-file=3 \\ 82 | registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:$SRS_MAJOR 83 | END 84 | bash /root/restart_docs-signaling.sh 85 | # 86 | # Cleanup old docker images. 87 | for image in $(docker images |grep '' |awk '{print $3}'); do 88 | docker rmi -f $image 89 | echo "Remove image $image, r0=$?" 90 | done 91 | 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .format.txt 18 | objs 19 | .DS_Store 20 | .idea 21 | 22 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/signaling.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | ############################################################ 3 | # build 4 | ############################################################ 5 | FROM registry.cn-hangzhou.aliyuncs.com/ossrs/srs:ubuntu20 AS build 6 | 7 | COPY . /tmp/signaling 8 | RUN cd /tmp/signaling && make 9 | RUN cp /tmp/signaling/objs/signaling /usr/local/bin/signaling 10 | RUN cp -R /tmp/signaling/www /usr/local/ 11 | 12 | ############################################################ 13 | # dist 14 | ############################################################ 15 | FROM ubuntu:focal AS dist 16 | 17 | # HTTP/1989 18 | EXPOSE 1989 19 | # SRS binary, config files and srs-console. 20 | COPY --from=build /usr/local/bin/signaling /usr/local/bin/ 21 | COPY --from=build /usr/local/www /usr/local/www 22 | # Default workdir and command. 23 | WORKDIR /usr/local 24 | CMD ["./bin/signaling"] 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 srs-org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help default clean signaling 2 | 3 | default: signaling 4 | 5 | clean: 6 | rm -f ./objs/signaling 7 | 8 | .format.txt: *.go 9 | gofmt -w . 10 | echo "done" > .format.txt 11 | 12 | signaling: ./objs/signaling 13 | 14 | ./objs/signaling: .format.txt *.go Makefile 15 | go build -mod=vendor -o objs/signaling . 16 | 17 | help: 18 | @echo "Usage: make [signaling]" 19 | @echo " signaling Make the signaling to ./objs/signaling" 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # signaling 2 | 3 | A demo WebRTC signaling for https://github.com/ossrs/srs 4 | 5 | ## Usage 6 | 7 | [Run SRS](https://github.com/ossrs/srs/tree/4.0release#usage) in docker: 8 | 9 | ```bash 10 | docker run --rm --env CANDIDATE=$(ifconfig en0 inet| grep 'inet '|awk '{print $2}') \ 11 | -p 1935:1935 -p 8080:8080 -p 1985:1985 -p 8000:8000/udp \ 12 | registry.cn-hangzhou.aliyuncs.com/ossrs/srs:4 \ 13 | objs/srs -c conf/rtc.conf 14 | ``` 15 | 16 | > Note: More images and version is [here](https://cr.console.aliyun.com/repository/cn-hangzhou/ossrs/srs/images). 17 | 18 | Run signaling in docker: 19 | 20 | ```bash 21 | docker run --rm -p 1989:1989 registry.cn-hangzhou.aliyuncs.com/ossrs/signaling:1 22 | ``` 23 | 24 | > Note: More images and version is [here](https://cr.console.aliyun.com/repository/cn-hangzhou/ossrs/signaling/images). 25 | 26 | Open the H5 demos: 27 | 28 | * [WebRTC: One to One over SFU(SRS)](http://localhost:1989/demos/one2one.html?autostart=true) 29 | 30 | ## Build from source 31 | 32 | Build and [run SRS](https://github.com/ossrs/srs/tree/4.0release#usage): 33 | 34 | ```bash 35 | cd ~/git && git clone -b 4.0release https://gitee.com/ossrs/srs.git srs && 36 | cd ~/git/srs/trunk && ./configure && make && ./objs/srs -c conf/rtc.conf 37 | ``` 38 | 39 | Build and run signaling: 40 | 41 | ```bash 42 | cd ~/git/srs/trunk/3rdparty/signaling && make && ./objs/signaling 43 | ``` 44 | 45 | Open demos by localhost: http://localhost:1989/demos 46 | 47 | Build and run httpx-static for HTTPS/WSS: 48 | 49 | ```bash 50 | cd ~/git/srs/trunk/3rdparty/httpx-static && make && 51 | ./objs/httpx-static -http 80 -https 443 -ssk server.key -ssc server.crt \ 52 | -proxy http://127.0.0.1:1989/sig -proxy http://127.0.0.1:1985/rtc \ 53 | -proxy http://127.0.0.1:8080/ 54 | ``` 55 | 56 | Open demos by HTTPS or IP: 57 | 58 | * http://localhost/demos/ 59 | * https://localhost/demos/ 60 | * https://192.168.3.6/demos/ 61 | 62 | Winlin 2021.05 63 | 64 | 65 | -------------------------------------------------------------------------------- /auto/pub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REALPATH=$(realpath $0) 4 | WORK_DIR=$(cd $(dirname $REALPATH)/.. && pwd) 5 | echo "Run pub at $WORK_DIR from $0" 6 | cd $WORK_DIR 7 | 8 | git st |grep -q 'nothing to commit' 9 | if [[ $? -ne 0 ]]; then 10 | echo "Failed: Please commit before release"; 11 | exit 1 12 | fi 13 | 14 | RELEASE=$(git describe --tags --abbrev=0 --exclude release-*) 15 | REVISION=$(echo $RELEASE|awk -F . '{print $3}') 16 | let NEXT=$REVISION+1 17 | echo "Last release is $RELEASE, revision is $REVISION, next is $NEXT" 18 | 19 | TAG="v1.0.$NEXT" 20 | echo "publish $TAG" 21 | 22 | git push 23 | git tag -d $TAG 2>/dev/null && git push origin :$TAG 24 | git tag $TAG 25 | git push origin $TAG 26 | echo "publish $TAG ok" 27 | echo " https://github.com/ossrs/signaling/actions" 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ossrs/signaling 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/ossrs/go-oryx-lib v0.0.8 7 | golang.org/x/net v0.0.0-20210502030024-e5908800b52b 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ossrs/go-oryx-lib v0.0.8 h1:k8ml3ZLsjIMoQEdZdWuy8zkU0w/fbJSyHvT/s9NyeCc= 2 | github.com/ossrs/go-oryx-lib v0.0.8/go.mod h1:i2tH4TZBzAw5h+HwGrNOKvP/nmZgSQz0OEnLLdzcT/8= 3 | golang.org/x/net v0.0.0-20210502030024-e5908800b52b h1:jCRjgm6WJHzM8VQrm/es2wXYqqbq0NZ1yXFHHgzkiVQ= 4 | golang.org/x/net v0.0.0-20210502030024-e5908800b52b/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 5 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 6 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 7 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 8 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 9 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2021 Winlin 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | package main 22 | 23 | import ( 24 | "context" 25 | "encoding/json" 26 | "flag" 27 | "fmt" 28 | "net/http" 29 | "os" 30 | "path" 31 | "strings" 32 | "sync" 33 | 34 | "github.com/ossrs/go-oryx-lib/errors" 35 | "github.com/ossrs/go-oryx-lib/logger" 36 | "golang.org/x/net/websocket" 37 | ) 38 | 39 | type Participant struct { 40 | Room *Room `json:"-"` 41 | Display string `json:"display"` 42 | Publishing bool `json:"publishing"` 43 | Out chan []byte `json:"-"` 44 | } 45 | 46 | func (v *Participant) String() string { 47 | return fmt.Sprintf("display=%v, room=%v", v.Display, v.Room.Name) 48 | } 49 | 50 | type Room struct { 51 | Name string `json:"room"` 52 | Participants []*Participant `json:"participants"` 53 | lock sync.RWMutex `json:"-"` 54 | } 55 | 56 | func (v *Room) String() string { 57 | return fmt.Sprintf("room=%v, participants=%v", v.Name, len(v.Participants)) 58 | } 59 | 60 | func (v *Room) Add(p *Participant) error { 61 | v.lock.Lock() 62 | defer v.lock.Unlock() 63 | 64 | for _, r := range v.Participants { 65 | if r.Display == p.Display { 66 | return errors.Errorf("Participant %v exists in room %v", p.Display, v.Name) 67 | } 68 | } 69 | 70 | v.Participants = append(v.Participants, p) 71 | return nil 72 | } 73 | 74 | func (v *Room) Get(display string) *Participant { 75 | v.lock.RLock() 76 | defer v.lock.RUnlock() 77 | 78 | for _, r := range v.Participants { 79 | if r.Display == display { 80 | return r 81 | } 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func (v *Room) Remove(p *Participant) { 88 | v.lock.Lock() 89 | defer v.lock.Unlock() 90 | 91 | for i, r := range v.Participants { 92 | if p == r { 93 | v.Participants = append(v.Participants[:i], v.Participants[i+1:]...) 94 | return 95 | } 96 | } 97 | } 98 | 99 | func (v *Room) Notify(ctx context.Context, peer *Participant, event, param, data string) { 100 | var participants []*Participant 101 | func() { 102 | v.lock.RLock() 103 | defer v.lock.RUnlock() 104 | participants = append(participants, v.Participants...) 105 | }() 106 | 107 | for _, r := range participants { 108 | if r == peer { 109 | continue 110 | } 111 | 112 | res := struct { 113 | Action string `json:"action"` 114 | Event string `json:"event"` 115 | Param string `json:"param,omitempty"` 116 | Data string `json:"data,omitempty"` 117 | Room string `json:"room"` 118 | Self *Participant `json:"self"` 119 | Peer *Participant `json:"peer"` 120 | Participants []*Participant `json:"participants"` 121 | }{ 122 | "notify", event, param, data, 123 | v.Name, r, peer, participants, 124 | } 125 | 126 | b, err := json.Marshal(struct { 127 | Message interface{} `json:"msg"` 128 | }{ 129 | res, 130 | }) 131 | if err != nil { 132 | return 133 | } 134 | 135 | select { 136 | case <-ctx.Done(): 137 | return 138 | case r.Out <- b: 139 | } 140 | 141 | logger.Tf(ctx, "Notify %v about %v %v", r, peer, event) 142 | } 143 | } 144 | 145 | func main() { 146 | var listen string 147 | flag.StringVar(&listen, "listen", "1989", "The TCP listen port") 148 | 149 | var html string 150 | flag.StringVar(&html, "root", "./www", "The www web root") 151 | 152 | flag.Usage = func() { 153 | fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0])) 154 | fmt.Println(fmt.Sprintf("Options:")) 155 | fmt.Println(fmt.Sprintf(" -listen The TCP listen port. Default: %v", listen)) 156 | fmt.Println(fmt.Sprintf(" -root The www web root. Default: %v", html)) 157 | fmt.Println(fmt.Sprintf("For example:")) 158 | fmt.Println(fmt.Sprintf(" %v -listen %v -html %v", os.Args[0], listen, html)) 159 | } 160 | flag.Parse() 161 | 162 | if !strings.Contains(listen, ":") { 163 | listen = ":" + listen 164 | } 165 | 166 | ctx := context.Background() 167 | 168 | home := listen 169 | if strings.HasPrefix(home, ":") { 170 | home = "http://localhost" + listen 171 | } 172 | 173 | if !path.IsAbs(html) && path.IsAbs(os.Args[0]) { 174 | html = path.Join(path.Dir(os.Args[0]), html) 175 | } 176 | logger.Tf(ctx, "Signaling ok, root=%v, home page is %v", html, home) 177 | 178 | http.Handle("/", http.FileServer(http.Dir(html))) 179 | 180 | http.Handle("/sig/v1/versions", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 181 | w.Write([]byte("1.0")) 182 | })) 183 | 184 | // Key is name of room, value is Room 185 | var rooms sync.Map 186 | http.Handle("/sig/v1/rtc", websocket.Handler(func(c *websocket.Conn) { 187 | ctx, cancel := context.WithCancel(logger.WithContext(ctx)) 188 | defer cancel() 189 | 190 | r := c.Request() 191 | logger.Tf(ctx, "Serve client %v at %v", r.RemoteAddr, r.RequestURI) 192 | defer c.Close() 193 | 194 | var self *Participant 195 | go func() { 196 | <-ctx.Done() 197 | if self == nil { 198 | return 199 | } 200 | 201 | // Notify other peers that we're quiting. 202 | // @remark The ctx(of self) is done, so we must use a new context. 203 | go self.Room.Notify(context.Background(), self, "leave", "", "") 204 | 205 | self.Room.Remove(self) 206 | logger.Tf(ctx, "Remove client %v", self) 207 | }() 208 | 209 | inMessages := make(chan []byte, 0) 210 | go func() { 211 | defer cancel() 212 | 213 | buf := make([]byte, 16384) 214 | for { 215 | n, err := c.Read(buf) 216 | if err != nil { 217 | logger.Wf(ctx, "Ignore err %v for %v", err, r.RemoteAddr) 218 | break 219 | } 220 | 221 | select { 222 | case <-ctx.Done(): 223 | case inMessages <- buf[:n]: 224 | } 225 | } 226 | }() 227 | 228 | outMessages := make(chan []byte, 0) 229 | go func() { 230 | defer cancel() 231 | 232 | handleMessage := func(m []byte) error { 233 | action := struct { 234 | TID string `json:"tid"` 235 | Message struct { 236 | Action string `json:"action"` 237 | } `json:"msg"` 238 | }{} 239 | if err := json.Unmarshal(m, &action); err != nil { 240 | return errors.Wrapf(err, "Unmarshal %s", m) 241 | } 242 | 243 | var res interface{} 244 | if action.Message.Action == "join" { 245 | obj := struct { 246 | Message struct { 247 | Room string `json:"room"` 248 | Display string `json:"display"` 249 | } `json:"msg"` 250 | }{} 251 | if err := json.Unmarshal(m, &obj); err != nil { 252 | return errors.Wrapf(err, "Unmarshal %s", m) 253 | } 254 | 255 | r, _ := rooms.LoadOrStore(obj.Message.Room, &Room{Name: obj.Message.Room}) 256 | p := &Participant{Room: r.(*Room), Display: obj.Message.Display, Out: outMessages} 257 | if err := r.(*Room).Add(p); err != nil { 258 | return errors.Wrapf(err, "join") 259 | } 260 | 261 | self = p 262 | logger.Tf(ctx, "Join %v ok", self) 263 | 264 | res = struct { 265 | Action string `json:"action"` 266 | Room string `json:"room"` 267 | Self *Participant `json:"self"` 268 | Participants []*Participant `json:"participants"` 269 | }{ 270 | action.Message.Action, obj.Message.Room, p, r.(*Room).Participants, 271 | } 272 | 273 | go r.(*Room).Notify(ctx, p, action.Message.Action, "", "") 274 | } else if action.Message.Action == "publish" { 275 | obj := struct { 276 | Message struct { 277 | Room string `json:"room"` 278 | Display string `json:"display"` 279 | } `json:"msg"` 280 | }{} 281 | if err := json.Unmarshal(m, &obj); err != nil { 282 | return errors.Wrapf(err, "Unmarshal %s", m) 283 | } 284 | 285 | r, _ := rooms.LoadOrStore(obj.Message.Room, &Room{Name: obj.Message.Room}) 286 | p := r.(*Room).Get(obj.Message.Display) 287 | 288 | // Now, the peer is publishing. 289 | p.Publishing = true 290 | 291 | go r.(*Room).Notify(ctx, p, action.Message.Action, "", "") 292 | } else if action.Message.Action == "control" { 293 | obj := struct { 294 | Message struct { 295 | Room string `json:"room"` 296 | Display string `json:"display"` 297 | Call string `json:"call"` 298 | Data string `json:"data"` 299 | } `json:"msg"` 300 | }{} 301 | if err := json.Unmarshal(m, &obj); err != nil { 302 | return errors.Wrapf(err, "Unmarshal %s", m) 303 | } 304 | 305 | r, _ := rooms.LoadOrStore(obj.Message.Room, &Room{Name: obj.Message.Room}) 306 | p := r.(*Room).Get(obj.Message.Display) 307 | 308 | go r.(*Room).Notify(ctx, p, action.Message.Action, obj.Message.Call, obj.Message.Data) 309 | } else { 310 | return errors.Errorf("Invalid message %s", m) 311 | } 312 | 313 | if b, err := json.Marshal(struct { 314 | TID string `json:"tid"` 315 | Message interface{} `json:"msg"` 316 | }{ 317 | action.TID, res, 318 | }); err != nil { 319 | return errors.Wrapf(err, "marshal") 320 | } else { 321 | select { 322 | case <-ctx.Done(): 323 | return ctx.Err() 324 | case outMessages <- b: 325 | } 326 | } 327 | 328 | return nil 329 | } 330 | 331 | for m := range inMessages { 332 | if err := handleMessage(m); err != nil { 333 | logger.Wf(ctx, "Handle %s err %v", m, err) 334 | break 335 | } 336 | } 337 | }() 338 | 339 | for m := range outMessages { 340 | if _, err := c.Write(m); err != nil { 341 | logger.Wf(ctx, "Ignore err %v for %v", err, r.RemoteAddr) 342 | break 343 | } 344 | } 345 | })) 346 | 347 | http.ListenAndServe(listen, nil) 348 | } 349 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 winlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/errors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/errors/README.md: -------------------------------------------------------------------------------- 1 | # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) 2 | 3 | Package errors provides simple error handling primitives. 4 | 5 | `go get github.com/pkg/errors` 6 | 7 | The traditional error handling idiom in Go is roughly akin to 8 | ```go 9 | if err != nil { 10 | return err 11 | } 12 | ``` 13 | which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. 14 | 15 | ## Adding context to an error 16 | 17 | The errors.Wrap function returns a new error that adds context to the original error. For example 18 | ```go 19 | _, err := ioutil.ReadAll(r) 20 | if err != nil { 21 | return errors.Wrap(err, "read failed") 22 | } 23 | ``` 24 | ## Retrieving the cause of an error 25 | 26 | Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. 27 | ```go 28 | type causer interface { 29 | Cause() error 30 | } 31 | ``` 32 | `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: 33 | ```go 34 | switch err := errors.Cause(err).(type) { 35 | case *MyError: 36 | // handle specifically 37 | default: 38 | // unknown error 39 | } 40 | ``` 41 | 42 | [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). 43 | 44 | ## Contributing 45 | 46 | We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. 47 | 48 | Before proposing a change, please discuss your change by raising an issue. 49 | 50 | ## Licence 51 | 52 | BSD-2-Clause 53 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides simple error handling primitives. 2 | // 3 | // The traditional error handling idiom in Go is roughly akin to 4 | // 5 | // if err != nil { 6 | // return err 7 | // } 8 | // 9 | // which applied recursively up the call stack results in error reports 10 | // without context or debugging information. The errors package allows 11 | // programmers to add context to the failure path in their code in a way 12 | // that does not destroy the original value of the error. 13 | // 14 | // Adding context to an error 15 | // 16 | // The errors.Wrap function returns a new error that adds context to the 17 | // original error by recording a stack trace at the point Wrap is called, 18 | // and the supplied message. For example 19 | // 20 | // _, err := ioutil.ReadAll(r) 21 | // if err != nil { 22 | // return errors.Wrap(err, "read failed") 23 | // } 24 | // 25 | // If additional control is required the errors.WithStack and errors.WithMessage 26 | // functions destructure errors.Wrap into its component operations of annotating 27 | // an error with a stack trace and an a message, respectively. 28 | // 29 | // Retrieving the cause of an error 30 | // 31 | // Using errors.Wrap constructs a stack of errors, adding context to the 32 | // preceding error. Depending on the nature of the error it may be necessary 33 | // to reverse the operation of errors.Wrap to retrieve the original error 34 | // for inspection. Any error value which implements this interface 35 | // 36 | // type causer interface { 37 | // Cause() error 38 | // } 39 | // 40 | // can be inspected by errors.Cause. errors.Cause will recursively retrieve 41 | // the topmost error which does not implement causer, which is assumed to be 42 | // the original cause. For example: 43 | // 44 | // switch err := errors.Cause(err).(type) { 45 | // case *MyError: 46 | // // handle specifically 47 | // default: 48 | // // unknown error 49 | // } 50 | // 51 | // causer interface is not exported by this package, but is considered a part 52 | // of stable public API. 53 | // 54 | // Formatted printing of errors 55 | // 56 | // All error values returned from this package implement fmt.Formatter and can 57 | // be formatted by the fmt package. The following verbs are supported 58 | // 59 | // %s print the error. If the error has a Cause it will be 60 | // printed recursively 61 | // %v see %s 62 | // %+v extended format. Each Frame of the error's StackTrace will 63 | // be printed in detail. 64 | // 65 | // Retrieving the stack trace of an error or wrapper 66 | // 67 | // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are 68 | // invoked. This information can be retrieved with the following interface. 69 | // 70 | // type stackTracer interface { 71 | // StackTrace() errors.StackTrace 72 | // } 73 | // 74 | // Where errors.StackTrace is defined as 75 | // 76 | // type StackTrace []Frame 77 | // 78 | // The Frame type represents a call site in the stack trace. Frame supports 79 | // the fmt.Formatter interface that can be used for printing information about 80 | // the stack trace of this error. For example: 81 | // 82 | // if err, ok := err.(stackTracer); ok { 83 | // for _, f := range err.StackTrace() { 84 | // fmt.Printf("%+s:%d", f) 85 | // } 86 | // } 87 | // 88 | // stackTracer interface is not exported by this package, but is considered a part 89 | // of stable public API. 90 | // 91 | // See the documentation for Frame.Format for more details. 92 | // Fork from https://github.com/pkg/errors 93 | package errors 94 | 95 | import ( 96 | "fmt" 97 | "io" 98 | ) 99 | 100 | // New returns an error with the supplied message. 101 | // New also records the stack trace at the point it was called. 102 | func New(message string) error { 103 | return &fundamental{ 104 | msg: message, 105 | stack: callers(), 106 | } 107 | } 108 | 109 | // Errorf formats according to a format specifier and returns the string 110 | // as a value that satisfies error. 111 | // Errorf also records the stack trace at the point it was called. 112 | func Errorf(format string, args ...interface{}) error { 113 | return &fundamental{ 114 | msg: fmt.Sprintf(format, args...), 115 | stack: callers(), 116 | } 117 | } 118 | 119 | // fundamental is an error that has a message and a stack, but no caller. 120 | type fundamental struct { 121 | msg string 122 | *stack 123 | } 124 | 125 | func (f *fundamental) Error() string { return f.msg } 126 | 127 | func (f *fundamental) Format(s fmt.State, verb rune) { 128 | switch verb { 129 | case 'v': 130 | if s.Flag('+') { 131 | io.WriteString(s, f.msg) 132 | f.stack.Format(s, verb) 133 | return 134 | } 135 | fallthrough 136 | case 's': 137 | io.WriteString(s, f.msg) 138 | case 'q': 139 | fmt.Fprintf(s, "%q", f.msg) 140 | } 141 | } 142 | 143 | // WithStack annotates err with a stack trace at the point WithStack was called. 144 | // If err is nil, WithStack returns nil. 145 | func WithStack(err error) error { 146 | if err == nil { 147 | return nil 148 | } 149 | return &withStack{ 150 | err, 151 | callers(), 152 | } 153 | } 154 | 155 | type withStack struct { 156 | error 157 | *stack 158 | } 159 | 160 | func (w *withStack) Cause() error { return w.error } 161 | 162 | func (w *withStack) Format(s fmt.State, verb rune) { 163 | switch verb { 164 | case 'v': 165 | if s.Flag('+') { 166 | fmt.Fprintf(s, "%+v", w.Cause()) 167 | w.stack.Format(s, verb) 168 | return 169 | } 170 | fallthrough 171 | case 's': 172 | io.WriteString(s, w.Error()) 173 | case 'q': 174 | fmt.Fprintf(s, "%q", w.Error()) 175 | } 176 | } 177 | 178 | // Wrap returns an error annotating err with a stack trace 179 | // at the point Wrap is called, and the supplied message. 180 | // If err is nil, Wrap returns nil. 181 | func Wrap(err error, message string) error { 182 | if err == nil { 183 | return nil 184 | } 185 | err = &withMessage{ 186 | cause: err, 187 | msg: message, 188 | } 189 | return &withStack{ 190 | err, 191 | callers(), 192 | } 193 | } 194 | 195 | // Wrapf returns an error annotating err with a stack trace 196 | // at the point Wrapf is call, and the format specifier. 197 | // If err is nil, Wrapf returns nil. 198 | func Wrapf(err error, format string, args ...interface{}) error { 199 | if err == nil { 200 | return nil 201 | } 202 | err = &withMessage{ 203 | cause: err, 204 | msg: fmt.Sprintf(format, args...), 205 | } 206 | return &withStack{ 207 | err, 208 | callers(), 209 | } 210 | } 211 | 212 | // WithMessage annotates err with a new message. 213 | // If err is nil, WithMessage returns nil. 214 | func WithMessage(err error, message string) error { 215 | if err == nil { 216 | return nil 217 | } 218 | return &withMessage{ 219 | cause: err, 220 | msg: message, 221 | } 222 | } 223 | 224 | type withMessage struct { 225 | cause error 226 | msg string 227 | } 228 | 229 | func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } 230 | func (w *withMessage) Cause() error { return w.cause } 231 | 232 | func (w *withMessage) Format(s fmt.State, verb rune) { 233 | switch verb { 234 | case 'v': 235 | if s.Flag('+') { 236 | fmt.Fprintf(s, "%+v\n", w.Cause()) 237 | io.WriteString(s, w.msg) 238 | return 239 | } 240 | fallthrough 241 | case 's', 'q': 242 | io.WriteString(s, w.Error()) 243 | } 244 | } 245 | 246 | // Cause returns the underlying cause of the error, if possible. 247 | // An error value has a cause if it implements the following 248 | // interface: 249 | // 250 | // type causer interface { 251 | // Cause() error 252 | // } 253 | // 254 | // If the error does not implement Cause, the original error will 255 | // be returned. If the error is nil, nil will be returned without further 256 | // investigation. 257 | func Cause(err error) error { 258 | type causer interface { 259 | Cause() error 260 | } 261 | 262 | for err != nil { 263 | cause, ok := err.(causer) 264 | if !ok { 265 | break 266 | } 267 | err = cause.Cause() 268 | } 269 | return err 270 | } 271 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/errors/stack.go: -------------------------------------------------------------------------------- 1 | // Fork from https://github.com/pkg/errors 2 | package errors 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "path" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | // Frame represents a program counter inside a stack frame. 13 | type Frame uintptr 14 | 15 | // pc returns the program counter for this frame; 16 | // multiple frames may have the same PC value. 17 | func (f Frame) pc() uintptr { return uintptr(f) - 1 } 18 | 19 | // file returns the full path to the file that contains the 20 | // function for this Frame's pc. 21 | func (f Frame) file() string { 22 | fn := runtime.FuncForPC(f.pc()) 23 | if fn == nil { 24 | return "unknown" 25 | } 26 | file, _ := fn.FileLine(f.pc()) 27 | return file 28 | } 29 | 30 | // line returns the line number of source code of the 31 | // function for this Frame's pc. 32 | func (f Frame) line() int { 33 | fn := runtime.FuncForPC(f.pc()) 34 | if fn == nil { 35 | return 0 36 | } 37 | _, line := fn.FileLine(f.pc()) 38 | return line 39 | } 40 | 41 | // Format formats the frame according to the fmt.Formatter interface. 42 | // 43 | // %s source file 44 | // %d source line 45 | // %n function name 46 | // %v equivalent to %s:%d 47 | // 48 | // Format accepts flags that alter the printing of some verbs, as follows: 49 | // 50 | // %+s path of source file relative to the compile time GOPATH 51 | // %+v equivalent to %+s:%d 52 | func (f Frame) Format(s fmt.State, verb rune) { 53 | switch verb { 54 | case 's': 55 | switch { 56 | case s.Flag('+'): 57 | pc := f.pc() 58 | fn := runtime.FuncForPC(pc) 59 | if fn == nil { 60 | io.WriteString(s, "unknown") 61 | } else { 62 | file, _ := fn.FileLine(pc) 63 | fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) 64 | } 65 | default: 66 | io.WriteString(s, path.Base(f.file())) 67 | } 68 | case 'd': 69 | fmt.Fprintf(s, "%d", f.line()) 70 | case 'n': 71 | name := runtime.FuncForPC(f.pc()).Name() 72 | io.WriteString(s, funcname(name)) 73 | case 'v': 74 | f.Format(s, 's') 75 | io.WriteString(s, ":") 76 | f.Format(s, 'd') 77 | } 78 | } 79 | 80 | // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). 81 | type StackTrace []Frame 82 | 83 | // Format formats the stack of Frames according to the fmt.Formatter interface. 84 | // 85 | // %s lists source files for each Frame in the stack 86 | // %v lists the source file and line number for each Frame in the stack 87 | // 88 | // Format accepts flags that alter the printing of some verbs, as follows: 89 | // 90 | // %+v Prints filename, function, and line number for each Frame in the stack. 91 | func (st StackTrace) Format(s fmt.State, verb rune) { 92 | switch verb { 93 | case 'v': 94 | switch { 95 | case s.Flag('+'): 96 | for _, f := range st { 97 | fmt.Fprintf(s, "\n%+v", f) 98 | } 99 | case s.Flag('#'): 100 | fmt.Fprintf(s, "%#v", []Frame(st)) 101 | default: 102 | fmt.Fprintf(s, "%v", []Frame(st)) 103 | } 104 | case 's': 105 | fmt.Fprintf(s, "%s", []Frame(st)) 106 | } 107 | } 108 | 109 | // stack represents a stack of program counters. 110 | type stack []uintptr 111 | 112 | func (s *stack) Format(st fmt.State, verb rune) { 113 | switch verb { 114 | case 'v': 115 | switch { 116 | case st.Flag('+'): 117 | for _, pc := range *s { 118 | f := Frame(pc) 119 | fmt.Fprintf(st, "\n%+v", f) 120 | } 121 | } 122 | } 123 | } 124 | 125 | func (s *stack) StackTrace() StackTrace { 126 | f := make([]Frame, len(*s)) 127 | for i := 0; i < len(f); i++ { 128 | f[i] = Frame((*s)[i]) 129 | } 130 | return f 131 | } 132 | 133 | func callers() *stack { 134 | const depth = 32 135 | var pcs [depth]uintptr 136 | n := runtime.Callers(3, pcs[:]) 137 | var st stack = pcs[0:n] 138 | return &st 139 | } 140 | 141 | // funcname removes the path prefix component of a function's name reported by func.Name(). 142 | func funcname(name string) string { 143 | i := strings.LastIndex(name, "/") 144 | name = name[i+1:] 145 | i = strings.Index(name, ".") 146 | return name[i+1:] 147 | } 148 | 149 | func trimGOPATH(name, file string) string { 150 | // Here we want to get the source file path relative to the compile time 151 | // GOPATH. As of Go 1.6.x there is no direct way to know the compiled 152 | // GOPATH at runtime, but we can infer the number of path segments in the 153 | // GOPATH. We note that fn.Name() returns the function name qualified by 154 | // the import path, which does not include the GOPATH. Thus we can trim 155 | // segments from the beginning of the file path until the number of path 156 | // separators remaining is one more than the number of path separators in 157 | // the function name. For example, given: 158 | // 159 | // GOPATH /home/user 160 | // file /home/user/src/pkg/sub/file.go 161 | // fn.Name() pkg/sub.Type.Method 162 | // 163 | // We want to produce: 164 | // 165 | // pkg/sub/file.go 166 | // 167 | // From this we can easily see that fn.Name() has one less path separator 168 | // than our desired output. We count separators from the end of the file 169 | // path until it finds two more than in the function name and then move 170 | // one character forward to preserve the initial path segment without a 171 | // leading separator. 172 | const sep = "/" 173 | goal := strings.Count(name, sep) + 2 174 | i := len(file) 175 | for n := 0; n < goal; n++ { 176 | i = strings.LastIndex(file[:i], sep) 177 | if i == -1 { 178 | // not enough separators found, set i so that the slice expression 179 | // below leaves file unmodified 180 | i = -len(sep) 181 | break 182 | } 183 | } 184 | // get back to 0 or trim the leading separator 185 | file = file[i+len(sep):] 186 | return file 187 | } 188 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/logger/go17.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2013-2017 Oryx(ossrs) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // +build go1.7 23 | 24 | package logger 25 | 26 | import ( 27 | "context" 28 | "fmt" 29 | "os" 30 | ) 31 | 32 | func (v *loggerPlus) Println(ctx Context, a ...interface{}) { 33 | args := v.contextFormat(ctx, a...) 34 | v.doPrintln(args...) 35 | } 36 | 37 | func (v *loggerPlus) Printf(ctx Context, format string, a ...interface{}) { 38 | format, args := v.contextFormatf(ctx, format, a...) 39 | v.doPrintf(format, args...) 40 | } 41 | 42 | func (v *loggerPlus) contextFormat(ctx Context, a ...interface{}) []interface{} { 43 | if ctx, ok := ctx.(context.Context); ok { 44 | if cid, ok := ctx.Value(cidKey).(int); ok { 45 | return append([]interface{}{fmt.Sprintf("[%v][%v]", os.Getpid(), cid)}, a...) 46 | } 47 | } else { 48 | return v.format(ctx, a...) 49 | } 50 | return a 51 | } 52 | 53 | func (v *loggerPlus) contextFormatf(ctx Context, format string, a ...interface{}) (string, []interface{}) { 54 | if ctx, ok := ctx.(context.Context); ok { 55 | if cid, ok := ctx.Value(cidKey).(int); ok { 56 | return "[%v][%v] " + format, append([]interface{}{os.Getpid(), cid}, a...) 57 | } 58 | } else { 59 | return v.formatf(ctx, format, a...) 60 | } 61 | return format, a 62 | } 63 | 64 | // User should use context with value to pass the cid. 65 | type key string 66 | 67 | var cidKey key = "cid.logger.ossrs.org" 68 | 69 | var gCid int = 999 70 | 71 | // Create context with value. 72 | func WithContext(ctx context.Context) context.Context { 73 | gCid += 1 74 | return context.WithValue(ctx, cidKey, gCid) 75 | } 76 | 77 | // Create context with value from parent, copy the cid from source context. 78 | // @remark Create new cid if source has no cid represent. 79 | func AliasContext(parent context.Context, source context.Context) context.Context { 80 | if source != nil { 81 | if cid, ok := source.Value(cidKey).(int); ok { 82 | return context.WithValue(parent, cidKey, cid) 83 | } 84 | } 85 | return WithContext(parent) 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/logger/logger.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2013-2017 Oryx(ossrs) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // The oryx logger package provides connection-oriented log service. 23 | // logger.I(ctx, ...) 24 | // logger.T(ctx, ...) 25 | // logger.W(ctx, ...) 26 | // logger.E(ctx, ...) 27 | // Or use format: 28 | // logger.If(ctx, format, ...) 29 | // logger.Tf(ctx, format, ...) 30 | // logger.Wf(ctx, format, ...) 31 | // logger.Ef(ctx, format, ...) 32 | // @remark the Context is optional thus can be nil. 33 | // @remark From 1.7+, the ctx could be context.Context, wrap by logger.WithContext, 34 | // please read ExampleLogger_ContextGO17(). 35 | package logger 36 | 37 | import ( 38 | "fmt" 39 | "io" 40 | "io/ioutil" 41 | "log" 42 | "os" 43 | ) 44 | 45 | // default level for logger. 46 | const ( 47 | logInfoLabel = "[info] " 48 | logTraceLabel = "[trace] " 49 | logWarnLabel = "[warn] " 50 | logErrorLabel = "[error] " 51 | ) 52 | 53 | // The context for current goroutine. 54 | // It maybe a cidContext or context.Context from GO1.7. 55 | // @remark Use logger.WithContext(ctx) to wrap the context. 56 | type Context interface{} 57 | 58 | // The context to get current coroutine cid. 59 | type cidContext interface { 60 | Cid() int 61 | } 62 | 63 | // the LOG+ which provides connection-based log. 64 | type loggerPlus struct { 65 | logger *log.Logger 66 | } 67 | 68 | func NewLoggerPlus(l *log.Logger) Logger { 69 | return &loggerPlus{logger: l} 70 | } 71 | 72 | func (v *loggerPlus) format(ctx Context, a ...interface{}) []interface{} { 73 | if ctx == nil { 74 | return append([]interface{}{fmt.Sprintf("[%v] ", os.Getpid())}, a...) 75 | } else if ctx, ok := ctx.(cidContext); ok { 76 | return append([]interface{}{fmt.Sprintf("[%v][%v] ", os.Getpid(), ctx.Cid())}, a...) 77 | } 78 | return a 79 | } 80 | 81 | func (v *loggerPlus) formatf(ctx Context, format string, a ...interface{}) (string, []interface{}) { 82 | if ctx == nil { 83 | return "[%v] " + format, append([]interface{}{os.Getpid()}, a...) 84 | } else if ctx, ok := ctx.(cidContext); ok { 85 | return "[%v][%v] " + format, append([]interface{}{os.Getpid(), ctx.Cid()}, a...) 86 | } 87 | return format, a 88 | } 89 | 90 | var colorYellow = "\033[33m" 91 | var colorRed = "\033[31m" 92 | var colorBlack = "\033[0m" 93 | 94 | func (v *loggerPlus) doPrintln(args ...interface{}) { 95 | if previousCloser == nil { 96 | if v == Error { 97 | fmt.Fprintf(os.Stdout, colorRed) 98 | v.logger.Println(args...) 99 | fmt.Fprintf(os.Stdout, colorBlack) 100 | } else if v == Warn { 101 | fmt.Fprintf(os.Stdout, colorYellow) 102 | v.logger.Println(args...) 103 | fmt.Fprintf(os.Stdout, colorBlack) 104 | } else { 105 | v.logger.Println(args...) 106 | } 107 | } else { 108 | v.logger.Println(args...) 109 | } 110 | } 111 | 112 | func (v *loggerPlus) doPrintf(format string, args ...interface{}) { 113 | if previousCloser == nil { 114 | if v == Error { 115 | fmt.Fprintf(os.Stdout, colorRed) 116 | v.logger.Printf(format, args...) 117 | fmt.Fprintf(os.Stdout, colorBlack) 118 | } else if v == Warn { 119 | fmt.Fprintf(os.Stdout, colorYellow) 120 | v.logger.Printf(format, args...) 121 | fmt.Fprintf(os.Stdout, colorBlack) 122 | } else { 123 | v.logger.Printf(format, args...) 124 | } 125 | } else { 126 | v.logger.Printf(format, args...) 127 | } 128 | } 129 | 130 | // Info, the verbose info level, very detail log, the lowest level, to discard. 131 | var Info Logger 132 | 133 | // Alias for Info level println. 134 | func I(ctx Context, a ...interface{}) { 135 | Info.Println(ctx, a...) 136 | } 137 | 138 | // Printf for Info level log. 139 | func If(ctx Context, format string, a ...interface{}) { 140 | Info.Printf(ctx, format, a...) 141 | } 142 | 143 | // Trace, the trace level, something important, the default log level, to stdout. 144 | var Trace Logger 145 | 146 | // Alias for Trace level println. 147 | func T(ctx Context, a ...interface{}) { 148 | Trace.Println(ctx, a...) 149 | } 150 | 151 | // Printf for Trace level log. 152 | func Tf(ctx Context, format string, a ...interface{}) { 153 | Trace.Printf(ctx, format, a...) 154 | } 155 | 156 | // Warn, the warning level, dangerous information, to Stdout. 157 | var Warn Logger 158 | 159 | // Alias for Warn level println. 160 | func W(ctx Context, a ...interface{}) { 161 | Warn.Println(ctx, a...) 162 | } 163 | 164 | // Printf for Warn level log. 165 | func Wf(ctx Context, format string, a ...interface{}) { 166 | Warn.Printf(ctx, format, a...) 167 | } 168 | 169 | // Error, the error level, fatal error things, ot Stdout. 170 | var Error Logger 171 | 172 | // Alias for Error level println. 173 | func E(ctx Context, a ...interface{}) { 174 | Error.Println(ctx, a...) 175 | } 176 | 177 | // Printf for Error level log. 178 | func Ef(ctx Context, format string, a ...interface{}) { 179 | Error.Printf(ctx, format, a...) 180 | } 181 | 182 | // The logger for oryx. 183 | type Logger interface { 184 | // Println for logger plus, 185 | // @param ctx the connection-oriented context, 186 | // or context.Context from GO1.7, or nil to ignore. 187 | Println(ctx Context, a ...interface{}) 188 | Printf(ctx Context, format string, a ...interface{}) 189 | } 190 | 191 | func init() { 192 | Info = NewLoggerPlus(log.New(ioutil.Discard, logInfoLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 193 | Trace = NewLoggerPlus(log.New(os.Stdout, logTraceLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 194 | Warn = NewLoggerPlus(log.New(os.Stderr, logWarnLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 195 | Error = NewLoggerPlus(log.New(os.Stderr, logErrorLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 196 | 197 | // init writer and closer. 198 | previousWriter = os.Stdout 199 | previousCloser = nil 200 | } 201 | 202 | // Switch the underlayer io. 203 | // @remark user must close previous io for logger never close it. 204 | func Switch(w io.Writer) io.Writer { 205 | // TODO: support level, default to trace here. 206 | Info = NewLoggerPlus(log.New(ioutil.Discard, logInfoLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 207 | Trace = NewLoggerPlus(log.New(w, logTraceLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 208 | Warn = NewLoggerPlus(log.New(w, logWarnLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 209 | Error = NewLoggerPlus(log.New(w, logErrorLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 210 | 211 | ow := previousWriter 212 | previousWriter = w 213 | 214 | if c, ok := w.(io.Closer); ok { 215 | previousCloser = c 216 | } 217 | 218 | return ow 219 | } 220 | 221 | // The previous underlayer io for logger. 222 | var previousCloser io.Closer 223 | var previousWriter io.Writer 224 | 225 | // The interface io.Closer 226 | // Cleanup the logger, discard any log util switch to fresh writer. 227 | func Close() (err error) { 228 | Info = NewLoggerPlus(log.New(ioutil.Discard, logInfoLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 229 | Trace = NewLoggerPlus(log.New(ioutil.Discard, logTraceLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 230 | Warn = NewLoggerPlus(log.New(ioutil.Discard, logWarnLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 231 | Error = NewLoggerPlus(log.New(ioutil.Discard, logErrorLabel, log.Ldate|log.Ltime|log.Lmicroseconds)) 232 | 233 | if previousCloser != nil { 234 | err = previousCloser.Close() 235 | previousCloser = nil 236 | } 237 | 238 | return 239 | } 240 | -------------------------------------------------------------------------------- /vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2013-2017 Oryx(ossrs) 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // +build !go1.7 23 | 24 | package logger 25 | 26 | func (v *loggerPlus) Println(ctx Context, a ...interface{}) { 27 | args := v.format(ctx, a...) 28 | v.doPrintln(args...) 29 | } 30 | 31 | func (v *loggerPlus) Printf(ctx Context, format string, a ...interface{}) { 32 | format, args := v.formatf(ctx, format, a...) 33 | v.doPrintf(format, args...) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/AUTHORS: -------------------------------------------------------------------------------- 1 | # This source code refers to The Go Authors for copyright purposes. 2 | # The master list of authors is in the main Go distribution, 3 | # visible at http://tip.golang.org/AUTHORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This source code was written by the Go contributors. 2 | # The master list of contributors is in the main Go distribution, 3 | # visible at http://tip.golang.org/CONTRIBUTORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/websocket/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | ) 14 | 15 | // DialError is an error that occurs while dialling a websocket server. 16 | type DialError struct { 17 | *Config 18 | Err error 19 | } 20 | 21 | func (e *DialError) Error() string { 22 | return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error() 23 | } 24 | 25 | // NewConfig creates a new WebSocket config for client connection. 26 | func NewConfig(server, origin string) (config *Config, err error) { 27 | config = new(Config) 28 | config.Version = ProtocolVersionHybi13 29 | config.Location, err = url.ParseRequestURI(server) 30 | if err != nil { 31 | return 32 | } 33 | config.Origin, err = url.ParseRequestURI(origin) 34 | if err != nil { 35 | return 36 | } 37 | config.Header = http.Header(make(map[string][]string)) 38 | return 39 | } 40 | 41 | // NewClient creates a new WebSocket client connection over rwc. 42 | func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) { 43 | br := bufio.NewReader(rwc) 44 | bw := bufio.NewWriter(rwc) 45 | err = hybiClientHandshake(config, br, bw) 46 | if err != nil { 47 | return 48 | } 49 | buf := bufio.NewReadWriter(br, bw) 50 | ws = newHybiClientConn(config, buf, rwc) 51 | return 52 | } 53 | 54 | // Dial opens a new client connection to a WebSocket. 55 | func Dial(url_, protocol, origin string) (ws *Conn, err error) { 56 | config, err := NewConfig(url_, origin) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if protocol != "" { 61 | config.Protocol = []string{protocol} 62 | } 63 | return DialConfig(config) 64 | } 65 | 66 | var portMap = map[string]string{ 67 | "ws": "80", 68 | "wss": "443", 69 | } 70 | 71 | func parseAuthority(location *url.URL) string { 72 | if _, ok := portMap[location.Scheme]; ok { 73 | if _, _, err := net.SplitHostPort(location.Host); err != nil { 74 | return net.JoinHostPort(location.Host, portMap[location.Scheme]) 75 | } 76 | } 77 | return location.Host 78 | } 79 | 80 | // DialConfig opens a new client connection to a WebSocket with a config. 81 | func DialConfig(config *Config) (ws *Conn, err error) { 82 | var client net.Conn 83 | if config.Location == nil { 84 | return nil, &DialError{config, ErrBadWebSocketLocation} 85 | } 86 | if config.Origin == nil { 87 | return nil, &DialError{config, ErrBadWebSocketOrigin} 88 | } 89 | dialer := config.Dialer 90 | if dialer == nil { 91 | dialer = &net.Dialer{} 92 | } 93 | client, err = dialWithDialer(dialer, config) 94 | if err != nil { 95 | goto Error 96 | } 97 | ws, err = NewClient(config, client) 98 | if err != nil { 99 | client.Close() 100 | goto Error 101 | } 102 | return 103 | 104 | Error: 105 | return nil, &DialError{config, err} 106 | } 107 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/websocket/dial.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/tls" 9 | "net" 10 | ) 11 | 12 | func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) { 13 | switch config.Location.Scheme { 14 | case "ws": 15 | conn, err = dialer.Dial("tcp", parseAuthority(config.Location)) 16 | 17 | case "wss": 18 | conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig) 19 | 20 | default: 21 | err = ErrBadScheme 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/websocket/hybi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | // This file implements a protocol of hybi draft. 8 | // http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 9 | 10 | import ( 11 | "bufio" 12 | "bytes" 13 | "crypto/rand" 14 | "crypto/sha1" 15 | "encoding/base64" 16 | "encoding/binary" 17 | "fmt" 18 | "io" 19 | "io/ioutil" 20 | "net/http" 21 | "net/url" 22 | "strings" 23 | ) 24 | 25 | const ( 26 | websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 27 | 28 | closeStatusNormal = 1000 29 | closeStatusGoingAway = 1001 30 | closeStatusProtocolError = 1002 31 | closeStatusUnsupportedData = 1003 32 | closeStatusFrameTooLarge = 1004 33 | closeStatusNoStatusRcvd = 1005 34 | closeStatusAbnormalClosure = 1006 35 | closeStatusBadMessageData = 1007 36 | closeStatusPolicyViolation = 1008 37 | closeStatusTooBigData = 1009 38 | closeStatusExtensionMismatch = 1010 39 | 40 | maxControlFramePayloadLength = 125 41 | ) 42 | 43 | var ( 44 | ErrBadMaskingKey = &ProtocolError{"bad masking key"} 45 | ErrBadPongMessage = &ProtocolError{"bad pong message"} 46 | ErrBadClosingStatus = &ProtocolError{"bad closing status"} 47 | ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"} 48 | ErrNotImplemented = &ProtocolError{"not implemented"} 49 | 50 | handshakeHeader = map[string]bool{ 51 | "Host": true, 52 | "Upgrade": true, 53 | "Connection": true, 54 | "Sec-Websocket-Key": true, 55 | "Sec-Websocket-Origin": true, 56 | "Sec-Websocket-Version": true, 57 | "Sec-Websocket-Protocol": true, 58 | "Sec-Websocket-Accept": true, 59 | } 60 | ) 61 | 62 | // A hybiFrameHeader is a frame header as defined in hybi draft. 63 | type hybiFrameHeader struct { 64 | Fin bool 65 | Rsv [3]bool 66 | OpCode byte 67 | Length int64 68 | MaskingKey []byte 69 | 70 | data *bytes.Buffer 71 | } 72 | 73 | // A hybiFrameReader is a reader for hybi frame. 74 | type hybiFrameReader struct { 75 | reader io.Reader 76 | 77 | header hybiFrameHeader 78 | pos int64 79 | length int 80 | } 81 | 82 | func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) { 83 | n, err = frame.reader.Read(msg) 84 | if frame.header.MaskingKey != nil { 85 | for i := 0; i < n; i++ { 86 | msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4] 87 | frame.pos++ 88 | } 89 | } 90 | return n, err 91 | } 92 | 93 | func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode } 94 | 95 | func (frame *hybiFrameReader) HeaderReader() io.Reader { 96 | if frame.header.data == nil { 97 | return nil 98 | } 99 | if frame.header.data.Len() == 0 { 100 | return nil 101 | } 102 | return frame.header.data 103 | } 104 | 105 | func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil } 106 | 107 | func (frame *hybiFrameReader) Len() (n int) { return frame.length } 108 | 109 | // A hybiFrameReaderFactory creates new frame reader based on its frame type. 110 | type hybiFrameReaderFactory struct { 111 | *bufio.Reader 112 | } 113 | 114 | // NewFrameReader reads a frame header from the connection, and creates new reader for the frame. 115 | // See Section 5.2 Base Framing protocol for detail. 116 | // http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2 117 | func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) { 118 | hybiFrame := new(hybiFrameReader) 119 | frame = hybiFrame 120 | var header []byte 121 | var b byte 122 | // First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits) 123 | b, err = buf.ReadByte() 124 | if err != nil { 125 | return 126 | } 127 | header = append(header, b) 128 | hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0 129 | for i := 0; i < 3; i++ { 130 | j := uint(6 - i) 131 | hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0 132 | } 133 | hybiFrame.header.OpCode = header[0] & 0x0f 134 | 135 | // Second byte. Mask/Payload len(7bits) 136 | b, err = buf.ReadByte() 137 | if err != nil { 138 | return 139 | } 140 | header = append(header, b) 141 | mask := (b & 0x80) != 0 142 | b &= 0x7f 143 | lengthFields := 0 144 | switch { 145 | case b <= 125: // Payload length 7bits. 146 | hybiFrame.header.Length = int64(b) 147 | case b == 126: // Payload length 7+16bits 148 | lengthFields = 2 149 | case b == 127: // Payload length 7+64bits 150 | lengthFields = 8 151 | } 152 | for i := 0; i < lengthFields; i++ { 153 | b, err = buf.ReadByte() 154 | if err != nil { 155 | return 156 | } 157 | if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits 158 | b &= 0x7f 159 | } 160 | header = append(header, b) 161 | hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b) 162 | } 163 | if mask { 164 | // Masking key. 4 bytes. 165 | for i := 0; i < 4; i++ { 166 | b, err = buf.ReadByte() 167 | if err != nil { 168 | return 169 | } 170 | header = append(header, b) 171 | hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b) 172 | } 173 | } 174 | hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length) 175 | hybiFrame.header.data = bytes.NewBuffer(header) 176 | hybiFrame.length = len(header) + int(hybiFrame.header.Length) 177 | return 178 | } 179 | 180 | // A HybiFrameWriter is a writer for hybi frame. 181 | type hybiFrameWriter struct { 182 | writer *bufio.Writer 183 | 184 | header *hybiFrameHeader 185 | } 186 | 187 | func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) { 188 | var header []byte 189 | var b byte 190 | if frame.header.Fin { 191 | b |= 0x80 192 | } 193 | for i := 0; i < 3; i++ { 194 | if frame.header.Rsv[i] { 195 | j := uint(6 - i) 196 | b |= 1 << j 197 | } 198 | } 199 | b |= frame.header.OpCode 200 | header = append(header, b) 201 | if frame.header.MaskingKey != nil { 202 | b = 0x80 203 | } else { 204 | b = 0 205 | } 206 | lengthFields := 0 207 | length := len(msg) 208 | switch { 209 | case length <= 125: 210 | b |= byte(length) 211 | case length < 65536: 212 | b |= 126 213 | lengthFields = 2 214 | default: 215 | b |= 127 216 | lengthFields = 8 217 | } 218 | header = append(header, b) 219 | for i := 0; i < lengthFields; i++ { 220 | j := uint((lengthFields - i - 1) * 8) 221 | b = byte((length >> j) & 0xff) 222 | header = append(header, b) 223 | } 224 | if frame.header.MaskingKey != nil { 225 | if len(frame.header.MaskingKey) != 4 { 226 | return 0, ErrBadMaskingKey 227 | } 228 | header = append(header, frame.header.MaskingKey...) 229 | frame.writer.Write(header) 230 | data := make([]byte, length) 231 | for i := range data { 232 | data[i] = msg[i] ^ frame.header.MaskingKey[i%4] 233 | } 234 | frame.writer.Write(data) 235 | err = frame.writer.Flush() 236 | return length, err 237 | } 238 | frame.writer.Write(header) 239 | frame.writer.Write(msg) 240 | err = frame.writer.Flush() 241 | return length, err 242 | } 243 | 244 | func (frame *hybiFrameWriter) Close() error { return nil } 245 | 246 | type hybiFrameWriterFactory struct { 247 | *bufio.Writer 248 | needMaskingKey bool 249 | } 250 | 251 | func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) { 252 | frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType} 253 | if buf.needMaskingKey { 254 | frameHeader.MaskingKey, err = generateMaskingKey() 255 | if err != nil { 256 | return nil, err 257 | } 258 | } 259 | return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil 260 | } 261 | 262 | type hybiFrameHandler struct { 263 | conn *Conn 264 | payloadType byte 265 | } 266 | 267 | func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) { 268 | if handler.conn.IsServerConn() { 269 | // The client MUST mask all frames sent to the server. 270 | if frame.(*hybiFrameReader).header.MaskingKey == nil { 271 | handler.WriteClose(closeStatusProtocolError) 272 | return nil, io.EOF 273 | } 274 | } else { 275 | // The server MUST NOT mask all frames. 276 | if frame.(*hybiFrameReader).header.MaskingKey != nil { 277 | handler.WriteClose(closeStatusProtocolError) 278 | return nil, io.EOF 279 | } 280 | } 281 | if header := frame.HeaderReader(); header != nil { 282 | io.Copy(ioutil.Discard, header) 283 | } 284 | switch frame.PayloadType() { 285 | case ContinuationFrame: 286 | frame.(*hybiFrameReader).header.OpCode = handler.payloadType 287 | case TextFrame, BinaryFrame: 288 | handler.payloadType = frame.PayloadType() 289 | case CloseFrame: 290 | return nil, io.EOF 291 | case PingFrame, PongFrame: 292 | b := make([]byte, maxControlFramePayloadLength) 293 | n, err := io.ReadFull(frame, b) 294 | if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { 295 | return nil, err 296 | } 297 | io.Copy(ioutil.Discard, frame) 298 | if frame.PayloadType() == PingFrame { 299 | if _, err := handler.WritePong(b[:n]); err != nil { 300 | return nil, err 301 | } 302 | } 303 | return nil, nil 304 | } 305 | return frame, nil 306 | } 307 | 308 | func (handler *hybiFrameHandler) WriteClose(status int) (err error) { 309 | handler.conn.wio.Lock() 310 | defer handler.conn.wio.Unlock() 311 | w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame) 312 | if err != nil { 313 | return err 314 | } 315 | msg := make([]byte, 2) 316 | binary.BigEndian.PutUint16(msg, uint16(status)) 317 | _, err = w.Write(msg) 318 | w.Close() 319 | return err 320 | } 321 | 322 | func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) { 323 | handler.conn.wio.Lock() 324 | defer handler.conn.wio.Unlock() 325 | w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame) 326 | if err != nil { 327 | return 0, err 328 | } 329 | n, err = w.Write(msg) 330 | w.Close() 331 | return n, err 332 | } 333 | 334 | // newHybiConn creates a new WebSocket connection speaking hybi draft protocol. 335 | func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { 336 | if buf == nil { 337 | br := bufio.NewReader(rwc) 338 | bw := bufio.NewWriter(rwc) 339 | buf = bufio.NewReadWriter(br, bw) 340 | } 341 | ws := &Conn{config: config, request: request, buf: buf, rwc: rwc, 342 | frameReaderFactory: hybiFrameReaderFactory{buf.Reader}, 343 | frameWriterFactory: hybiFrameWriterFactory{ 344 | buf.Writer, request == nil}, 345 | PayloadType: TextFrame, 346 | defaultCloseStatus: closeStatusNormal} 347 | ws.frameHandler = &hybiFrameHandler{conn: ws} 348 | return ws 349 | } 350 | 351 | // generateMaskingKey generates a masking key for a frame. 352 | func generateMaskingKey() (maskingKey []byte, err error) { 353 | maskingKey = make([]byte, 4) 354 | if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil { 355 | return 356 | } 357 | return 358 | } 359 | 360 | // generateNonce generates a nonce consisting of a randomly selected 16-byte 361 | // value that has been base64-encoded. 362 | func generateNonce() (nonce []byte) { 363 | key := make([]byte, 16) 364 | if _, err := io.ReadFull(rand.Reader, key); err != nil { 365 | panic(err) 366 | } 367 | nonce = make([]byte, 24) 368 | base64.StdEncoding.Encode(nonce, key) 369 | return 370 | } 371 | 372 | // removeZone removes IPv6 zone identifer from host. 373 | // E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" 374 | func removeZone(host string) string { 375 | if !strings.HasPrefix(host, "[") { 376 | return host 377 | } 378 | i := strings.LastIndex(host, "]") 379 | if i < 0 { 380 | return host 381 | } 382 | j := strings.LastIndex(host[:i], "%") 383 | if j < 0 { 384 | return host 385 | } 386 | return host[:j] + host[i:] 387 | } 388 | 389 | // getNonceAccept computes the base64-encoded SHA-1 of the concatenation of 390 | // the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string. 391 | func getNonceAccept(nonce []byte) (expected []byte, err error) { 392 | h := sha1.New() 393 | if _, err = h.Write(nonce); err != nil { 394 | return 395 | } 396 | if _, err = h.Write([]byte(websocketGUID)); err != nil { 397 | return 398 | } 399 | expected = make([]byte, 28) 400 | base64.StdEncoding.Encode(expected, h.Sum(nil)) 401 | return 402 | } 403 | 404 | // Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17 405 | func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) { 406 | bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n") 407 | 408 | // According to RFC 6874, an HTTP client, proxy, or other 409 | // intermediary must remove any IPv6 zone identifier attached 410 | // to an outgoing URI. 411 | bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n") 412 | bw.WriteString("Upgrade: websocket\r\n") 413 | bw.WriteString("Connection: Upgrade\r\n") 414 | nonce := generateNonce() 415 | if config.handshakeData != nil { 416 | nonce = []byte(config.handshakeData["key"]) 417 | } 418 | bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n") 419 | bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n") 420 | 421 | if config.Version != ProtocolVersionHybi13 { 422 | return ErrBadProtocolVersion 423 | } 424 | 425 | bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n") 426 | if len(config.Protocol) > 0 { 427 | bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n") 428 | } 429 | // TODO(ukai): send Sec-WebSocket-Extensions. 430 | err = config.Header.WriteSubset(bw, handshakeHeader) 431 | if err != nil { 432 | return err 433 | } 434 | 435 | bw.WriteString("\r\n") 436 | if err = bw.Flush(); err != nil { 437 | return err 438 | } 439 | 440 | resp, err := http.ReadResponse(br, &http.Request{Method: "GET"}) 441 | if err != nil { 442 | return err 443 | } 444 | if resp.StatusCode != 101 { 445 | return ErrBadStatus 446 | } 447 | if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" || 448 | strings.ToLower(resp.Header.Get("Connection")) != "upgrade" { 449 | return ErrBadUpgrade 450 | } 451 | expectedAccept, err := getNonceAccept(nonce) 452 | if err != nil { 453 | return err 454 | } 455 | if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) { 456 | return ErrChallengeResponse 457 | } 458 | if resp.Header.Get("Sec-WebSocket-Extensions") != "" { 459 | return ErrUnsupportedExtensions 460 | } 461 | offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol") 462 | if offeredProtocol != "" { 463 | protocolMatched := false 464 | for i := 0; i < len(config.Protocol); i++ { 465 | if config.Protocol[i] == offeredProtocol { 466 | protocolMatched = true 467 | break 468 | } 469 | } 470 | if !protocolMatched { 471 | return ErrBadWebSocketProtocol 472 | } 473 | config.Protocol = []string{offeredProtocol} 474 | } 475 | 476 | return nil 477 | } 478 | 479 | // newHybiClientConn creates a client WebSocket connection after handshake. 480 | func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn { 481 | return newHybiConn(config, buf, rwc, nil) 482 | } 483 | 484 | // A HybiServerHandshaker performs a server handshake using hybi draft protocol. 485 | type hybiServerHandshaker struct { 486 | *Config 487 | accept []byte 488 | } 489 | 490 | func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) { 491 | c.Version = ProtocolVersionHybi13 492 | if req.Method != "GET" { 493 | return http.StatusMethodNotAllowed, ErrBadRequestMethod 494 | } 495 | // HTTP version can be safely ignored. 496 | 497 | if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || 498 | !strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") { 499 | return http.StatusBadRequest, ErrNotWebSocket 500 | } 501 | 502 | key := req.Header.Get("Sec-Websocket-Key") 503 | if key == "" { 504 | return http.StatusBadRequest, ErrChallengeResponse 505 | } 506 | version := req.Header.Get("Sec-Websocket-Version") 507 | switch version { 508 | case "13": 509 | c.Version = ProtocolVersionHybi13 510 | default: 511 | return http.StatusBadRequest, ErrBadWebSocketVersion 512 | } 513 | var scheme string 514 | if req.TLS != nil { 515 | scheme = "wss" 516 | } else { 517 | scheme = "ws" 518 | } 519 | c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI()) 520 | if err != nil { 521 | return http.StatusBadRequest, err 522 | } 523 | protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol")) 524 | if protocol != "" { 525 | protocols := strings.Split(protocol, ",") 526 | for i := 0; i < len(protocols); i++ { 527 | c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i])) 528 | } 529 | } 530 | c.accept, err = getNonceAccept([]byte(key)) 531 | if err != nil { 532 | return http.StatusInternalServerError, err 533 | } 534 | return http.StatusSwitchingProtocols, nil 535 | } 536 | 537 | // Origin parses the Origin header in req. 538 | // If the Origin header is not set, it returns nil and nil. 539 | func Origin(config *Config, req *http.Request) (*url.URL, error) { 540 | var origin string 541 | switch config.Version { 542 | case ProtocolVersionHybi13: 543 | origin = req.Header.Get("Origin") 544 | } 545 | if origin == "" { 546 | return nil, nil 547 | } 548 | return url.ParseRequestURI(origin) 549 | } 550 | 551 | func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) { 552 | if len(c.Protocol) > 0 { 553 | if len(c.Protocol) != 1 { 554 | // You need choose a Protocol in Handshake func in Server. 555 | return ErrBadWebSocketProtocol 556 | } 557 | } 558 | buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n") 559 | buf.WriteString("Upgrade: websocket\r\n") 560 | buf.WriteString("Connection: Upgrade\r\n") 561 | buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n") 562 | if len(c.Protocol) > 0 { 563 | buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n") 564 | } 565 | // TODO(ukai): send Sec-WebSocket-Extensions. 566 | if c.Header != nil { 567 | err := c.Header.WriteSubset(buf, handshakeHeader) 568 | if err != nil { 569 | return err 570 | } 571 | } 572 | buf.WriteString("\r\n") 573 | return buf.Flush() 574 | } 575 | 576 | func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { 577 | return newHybiServerConn(c.Config, buf, rwc, request) 578 | } 579 | 580 | // newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol. 581 | func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { 582 | return newHybiConn(config, buf, rwc, request) 583 | } 584 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/websocket/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | ) 13 | 14 | func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) { 15 | var hs serverHandshaker = &hybiServerHandshaker{Config: config} 16 | code, err := hs.ReadHandshake(buf.Reader, req) 17 | if err == ErrBadWebSocketVersion { 18 | fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) 19 | fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion) 20 | buf.WriteString("\r\n") 21 | buf.WriteString(err.Error()) 22 | buf.Flush() 23 | return 24 | } 25 | if err != nil { 26 | fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) 27 | buf.WriteString("\r\n") 28 | buf.WriteString(err.Error()) 29 | buf.Flush() 30 | return 31 | } 32 | if handshake != nil { 33 | err = handshake(config, req) 34 | if err != nil { 35 | code = http.StatusForbidden 36 | fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) 37 | buf.WriteString("\r\n") 38 | buf.Flush() 39 | return 40 | } 41 | } 42 | err = hs.AcceptHandshake(buf.Writer) 43 | if err != nil { 44 | code = http.StatusBadRequest 45 | fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) 46 | buf.WriteString("\r\n") 47 | buf.Flush() 48 | return 49 | } 50 | conn = hs.NewServerConn(buf, rwc, req) 51 | return 52 | } 53 | 54 | // Server represents a server of a WebSocket. 55 | type Server struct { 56 | // Config is a WebSocket configuration for new WebSocket connection. 57 | Config 58 | 59 | // Handshake is an optional function in WebSocket handshake. 60 | // For example, you can check, or don't check Origin header. 61 | // Another example, you can select config.Protocol. 62 | Handshake func(*Config, *http.Request) error 63 | 64 | // Handler handles a WebSocket connection. 65 | Handler 66 | } 67 | 68 | // ServeHTTP implements the http.Handler interface for a WebSocket 69 | func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { 70 | s.serveWebSocket(w, req) 71 | } 72 | 73 | func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) { 74 | rwc, buf, err := w.(http.Hijacker).Hijack() 75 | if err != nil { 76 | panic("Hijack failed: " + err.Error()) 77 | } 78 | // The server should abort the WebSocket connection if it finds 79 | // the client did not send a handshake that matches with protocol 80 | // specification. 81 | defer rwc.Close() 82 | conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake) 83 | if err != nil { 84 | return 85 | } 86 | if conn == nil { 87 | panic("unexpected nil conn") 88 | } 89 | s.Handler(conn) 90 | } 91 | 92 | // Handler is a simple interface to a WebSocket browser client. 93 | // It checks if Origin header is valid URL by default. 94 | // You might want to verify websocket.Conn.Config().Origin in the func. 95 | // If you use Server instead of Handler, you could call websocket.Origin and 96 | // check the origin in your Handshake func. So, if you want to accept 97 | // non-browser clients, which do not send an Origin header, set a 98 | // Server.Handshake that does not check the origin. 99 | type Handler func(*Conn) 100 | 101 | func checkOrigin(config *Config, req *http.Request) (err error) { 102 | config.Origin, err = Origin(config, req) 103 | if err == nil && config.Origin == nil { 104 | return fmt.Errorf("null origin") 105 | } 106 | return err 107 | } 108 | 109 | // ServeHTTP implements the http.Handler interface for a WebSocket 110 | func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 111 | s := Server{Handler: h, Handshake: checkOrigin} 112 | s.serveWebSocket(w, req) 113 | } 114 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/websocket/websocket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package websocket implements a client and server for the WebSocket protocol 6 | // as specified in RFC 6455. 7 | // 8 | // This package currently lacks some features found in alternative 9 | // and more actively maintained WebSocket packages: 10 | // 11 | // https://godoc.org/github.com/gorilla/websocket 12 | // https://godoc.org/nhooyr.io/websocket 13 | package websocket // import "golang.org/x/net/websocket" 14 | 15 | import ( 16 | "bufio" 17 | "crypto/tls" 18 | "encoding/json" 19 | "errors" 20 | "io" 21 | "io/ioutil" 22 | "net" 23 | "net/http" 24 | "net/url" 25 | "sync" 26 | "time" 27 | ) 28 | 29 | const ( 30 | ProtocolVersionHybi13 = 13 31 | ProtocolVersionHybi = ProtocolVersionHybi13 32 | SupportedProtocolVersion = "13" 33 | 34 | ContinuationFrame = 0 35 | TextFrame = 1 36 | BinaryFrame = 2 37 | CloseFrame = 8 38 | PingFrame = 9 39 | PongFrame = 10 40 | UnknownFrame = 255 41 | 42 | DefaultMaxPayloadBytes = 32 << 20 // 32MB 43 | ) 44 | 45 | // ProtocolError represents WebSocket protocol errors. 46 | type ProtocolError struct { 47 | ErrorString string 48 | } 49 | 50 | func (err *ProtocolError) Error() string { return err.ErrorString } 51 | 52 | var ( 53 | ErrBadProtocolVersion = &ProtocolError{"bad protocol version"} 54 | ErrBadScheme = &ProtocolError{"bad scheme"} 55 | ErrBadStatus = &ProtocolError{"bad status"} 56 | ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"} 57 | ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"} 58 | ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"} 59 | ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"} 60 | ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"} 61 | ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"} 62 | ErrBadFrame = &ProtocolError{"bad frame"} 63 | ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"} 64 | ErrNotWebSocket = &ProtocolError{"not websocket protocol"} 65 | ErrBadRequestMethod = &ProtocolError{"bad method"} 66 | ErrNotSupported = &ProtocolError{"not supported"} 67 | ) 68 | 69 | // ErrFrameTooLarge is returned by Codec's Receive method if payload size 70 | // exceeds limit set by Conn.MaxPayloadBytes 71 | var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit") 72 | 73 | // Addr is an implementation of net.Addr for WebSocket. 74 | type Addr struct { 75 | *url.URL 76 | } 77 | 78 | // Network returns the network type for a WebSocket, "websocket". 79 | func (addr *Addr) Network() string { return "websocket" } 80 | 81 | // Config is a WebSocket configuration 82 | type Config struct { 83 | // A WebSocket server address. 84 | Location *url.URL 85 | 86 | // A Websocket client origin. 87 | Origin *url.URL 88 | 89 | // WebSocket subprotocols. 90 | Protocol []string 91 | 92 | // WebSocket protocol version. 93 | Version int 94 | 95 | // TLS config for secure WebSocket (wss). 96 | TlsConfig *tls.Config 97 | 98 | // Additional header fields to be sent in WebSocket opening handshake. 99 | Header http.Header 100 | 101 | // Dialer used when opening websocket connections. 102 | Dialer *net.Dialer 103 | 104 | handshakeData map[string]string 105 | } 106 | 107 | // serverHandshaker is an interface to handle WebSocket server side handshake. 108 | type serverHandshaker interface { 109 | // ReadHandshake reads handshake request message from client. 110 | // Returns http response code and error if any. 111 | ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) 112 | 113 | // AcceptHandshake accepts the client handshake request and sends 114 | // handshake response back to client. 115 | AcceptHandshake(buf *bufio.Writer) (err error) 116 | 117 | // NewServerConn creates a new WebSocket connection. 118 | NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) 119 | } 120 | 121 | // frameReader is an interface to read a WebSocket frame. 122 | type frameReader interface { 123 | // Reader is to read payload of the frame. 124 | io.Reader 125 | 126 | // PayloadType returns payload type. 127 | PayloadType() byte 128 | 129 | // HeaderReader returns a reader to read header of the frame. 130 | HeaderReader() io.Reader 131 | 132 | // TrailerReader returns a reader to read trailer of the frame. 133 | // If it returns nil, there is no trailer in the frame. 134 | TrailerReader() io.Reader 135 | 136 | // Len returns total length of the frame, including header and trailer. 137 | Len() int 138 | } 139 | 140 | // frameReaderFactory is an interface to creates new frame reader. 141 | type frameReaderFactory interface { 142 | NewFrameReader() (r frameReader, err error) 143 | } 144 | 145 | // frameWriter is an interface to write a WebSocket frame. 146 | type frameWriter interface { 147 | // Writer is to write payload of the frame. 148 | io.WriteCloser 149 | } 150 | 151 | // frameWriterFactory is an interface to create new frame writer. 152 | type frameWriterFactory interface { 153 | NewFrameWriter(payloadType byte) (w frameWriter, err error) 154 | } 155 | 156 | type frameHandler interface { 157 | HandleFrame(frame frameReader) (r frameReader, err error) 158 | WriteClose(status int) (err error) 159 | } 160 | 161 | // Conn represents a WebSocket connection. 162 | // 163 | // Multiple goroutines may invoke methods on a Conn simultaneously. 164 | type Conn struct { 165 | config *Config 166 | request *http.Request 167 | 168 | buf *bufio.ReadWriter 169 | rwc io.ReadWriteCloser 170 | 171 | rio sync.Mutex 172 | frameReaderFactory 173 | frameReader 174 | 175 | wio sync.Mutex 176 | frameWriterFactory 177 | 178 | frameHandler 179 | PayloadType byte 180 | defaultCloseStatus int 181 | 182 | // MaxPayloadBytes limits the size of frame payload received over Conn 183 | // by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used. 184 | MaxPayloadBytes int 185 | } 186 | 187 | // Read implements the io.Reader interface: 188 | // it reads data of a frame from the WebSocket connection. 189 | // if msg is not large enough for the frame data, it fills the msg and next Read 190 | // will read the rest of the frame data. 191 | // it reads Text frame or Binary frame. 192 | func (ws *Conn) Read(msg []byte) (n int, err error) { 193 | ws.rio.Lock() 194 | defer ws.rio.Unlock() 195 | again: 196 | if ws.frameReader == nil { 197 | frame, err := ws.frameReaderFactory.NewFrameReader() 198 | if err != nil { 199 | return 0, err 200 | } 201 | ws.frameReader, err = ws.frameHandler.HandleFrame(frame) 202 | if err != nil { 203 | return 0, err 204 | } 205 | if ws.frameReader == nil { 206 | goto again 207 | } 208 | } 209 | n, err = ws.frameReader.Read(msg) 210 | if err == io.EOF { 211 | if trailer := ws.frameReader.TrailerReader(); trailer != nil { 212 | io.Copy(ioutil.Discard, trailer) 213 | } 214 | ws.frameReader = nil 215 | goto again 216 | } 217 | return n, err 218 | } 219 | 220 | // Write implements the io.Writer interface: 221 | // it writes data as a frame to the WebSocket connection. 222 | func (ws *Conn) Write(msg []byte) (n int, err error) { 223 | ws.wio.Lock() 224 | defer ws.wio.Unlock() 225 | w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType) 226 | if err != nil { 227 | return 0, err 228 | } 229 | n, err = w.Write(msg) 230 | w.Close() 231 | return n, err 232 | } 233 | 234 | // Close implements the io.Closer interface. 235 | func (ws *Conn) Close() error { 236 | err := ws.frameHandler.WriteClose(ws.defaultCloseStatus) 237 | err1 := ws.rwc.Close() 238 | if err != nil { 239 | return err 240 | } 241 | return err1 242 | } 243 | 244 | // IsClientConn reports whether ws is a client-side connection. 245 | func (ws *Conn) IsClientConn() bool { return ws.request == nil } 246 | 247 | // IsServerConn reports whether ws is a server-side connection. 248 | func (ws *Conn) IsServerConn() bool { return ws.request != nil } 249 | 250 | // LocalAddr returns the WebSocket Origin for the connection for client, or 251 | // the WebSocket location for server. 252 | func (ws *Conn) LocalAddr() net.Addr { 253 | if ws.IsClientConn() { 254 | return &Addr{ws.config.Origin} 255 | } 256 | return &Addr{ws.config.Location} 257 | } 258 | 259 | // RemoteAddr returns the WebSocket location for the connection for client, or 260 | // the Websocket Origin for server. 261 | func (ws *Conn) RemoteAddr() net.Addr { 262 | if ws.IsClientConn() { 263 | return &Addr{ws.config.Location} 264 | } 265 | return &Addr{ws.config.Origin} 266 | } 267 | 268 | var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn") 269 | 270 | // SetDeadline sets the connection's network read & write deadlines. 271 | func (ws *Conn) SetDeadline(t time.Time) error { 272 | if conn, ok := ws.rwc.(net.Conn); ok { 273 | return conn.SetDeadline(t) 274 | } 275 | return errSetDeadline 276 | } 277 | 278 | // SetReadDeadline sets the connection's network read deadline. 279 | func (ws *Conn) SetReadDeadline(t time.Time) error { 280 | if conn, ok := ws.rwc.(net.Conn); ok { 281 | return conn.SetReadDeadline(t) 282 | } 283 | return errSetDeadline 284 | } 285 | 286 | // SetWriteDeadline sets the connection's network write deadline. 287 | func (ws *Conn) SetWriteDeadline(t time.Time) error { 288 | if conn, ok := ws.rwc.(net.Conn); ok { 289 | return conn.SetWriteDeadline(t) 290 | } 291 | return errSetDeadline 292 | } 293 | 294 | // Config returns the WebSocket config. 295 | func (ws *Conn) Config() *Config { return ws.config } 296 | 297 | // Request returns the http request upgraded to the WebSocket. 298 | // It is nil for client side. 299 | func (ws *Conn) Request() *http.Request { return ws.request } 300 | 301 | // Codec represents a symmetric pair of functions that implement a codec. 302 | type Codec struct { 303 | Marshal func(v interface{}) (data []byte, payloadType byte, err error) 304 | Unmarshal func(data []byte, payloadType byte, v interface{}) (err error) 305 | } 306 | 307 | // Send sends v marshaled by cd.Marshal as single frame to ws. 308 | func (cd Codec) Send(ws *Conn, v interface{}) (err error) { 309 | data, payloadType, err := cd.Marshal(v) 310 | if err != nil { 311 | return err 312 | } 313 | ws.wio.Lock() 314 | defer ws.wio.Unlock() 315 | w, err := ws.frameWriterFactory.NewFrameWriter(payloadType) 316 | if err != nil { 317 | return err 318 | } 319 | _, err = w.Write(data) 320 | w.Close() 321 | return err 322 | } 323 | 324 | // Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores 325 | // in v. The whole frame payload is read to an in-memory buffer; max size of 326 | // payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds 327 | // limit, ErrFrameTooLarge is returned; in this case frame is not read off wire 328 | // completely. The next call to Receive would read and discard leftover data of 329 | // previous oversized frame before processing next frame. 330 | func (cd Codec) Receive(ws *Conn, v interface{}) (err error) { 331 | ws.rio.Lock() 332 | defer ws.rio.Unlock() 333 | if ws.frameReader != nil { 334 | _, err = io.Copy(ioutil.Discard, ws.frameReader) 335 | if err != nil { 336 | return err 337 | } 338 | ws.frameReader = nil 339 | } 340 | again: 341 | frame, err := ws.frameReaderFactory.NewFrameReader() 342 | if err != nil { 343 | return err 344 | } 345 | frame, err = ws.frameHandler.HandleFrame(frame) 346 | if err != nil { 347 | return err 348 | } 349 | if frame == nil { 350 | goto again 351 | } 352 | maxPayloadBytes := ws.MaxPayloadBytes 353 | if maxPayloadBytes == 0 { 354 | maxPayloadBytes = DefaultMaxPayloadBytes 355 | } 356 | if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) { 357 | // payload size exceeds limit, no need to call Unmarshal 358 | // 359 | // set frameReader to current oversized frame so that 360 | // the next call to this function can drain leftover 361 | // data before processing the next frame 362 | ws.frameReader = frame 363 | return ErrFrameTooLarge 364 | } 365 | payloadType := frame.PayloadType() 366 | data, err := ioutil.ReadAll(frame) 367 | if err != nil { 368 | return err 369 | } 370 | return cd.Unmarshal(data, payloadType, v) 371 | } 372 | 373 | func marshal(v interface{}) (msg []byte, payloadType byte, err error) { 374 | switch data := v.(type) { 375 | case string: 376 | return []byte(data), TextFrame, nil 377 | case []byte: 378 | return data, BinaryFrame, nil 379 | } 380 | return nil, UnknownFrame, ErrNotSupported 381 | } 382 | 383 | func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) { 384 | switch data := v.(type) { 385 | case *string: 386 | *data = string(msg) 387 | return nil 388 | case *[]byte: 389 | *data = msg 390 | return nil 391 | } 392 | return ErrNotSupported 393 | } 394 | 395 | /* 396 | Message is a codec to send/receive text/binary data in a frame on WebSocket connection. 397 | To send/receive text frame, use string type. 398 | To send/receive binary frame, use []byte type. 399 | 400 | Trivial usage: 401 | 402 | import "websocket" 403 | 404 | // receive text frame 405 | var message string 406 | websocket.Message.Receive(ws, &message) 407 | 408 | // send text frame 409 | message = "hello" 410 | websocket.Message.Send(ws, message) 411 | 412 | // receive binary frame 413 | var data []byte 414 | websocket.Message.Receive(ws, &data) 415 | 416 | // send binary frame 417 | data = []byte{0, 1, 2} 418 | websocket.Message.Send(ws, data) 419 | 420 | */ 421 | var Message = Codec{marshal, unmarshal} 422 | 423 | func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) { 424 | msg, err = json.Marshal(v) 425 | return msg, TextFrame, err 426 | } 427 | 428 | func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) { 429 | return json.Unmarshal(msg, v) 430 | } 431 | 432 | /* 433 | JSON is a codec to send/receive JSON data in a frame from a WebSocket connection. 434 | 435 | Trivial usage: 436 | 437 | import "websocket" 438 | 439 | type T struct { 440 | Msg string 441 | Count int 442 | } 443 | 444 | // receive JSON type T 445 | var data T 446 | websocket.JSON.Receive(ws, &data) 447 | 448 | // send JSON type T 449 | websocket.JSON.Send(ws, data) 450 | */ 451 | var JSON = Codec{jsonMarshal, jsonUnmarshal} 452 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/ossrs/go-oryx-lib v0.0.8 2 | ## explicit 3 | github.com/ossrs/go-oryx-lib/errors 4 | github.com/ossrs/go-oryx-lib/logger 5 | # golang.org/x/net v0.0.0-20210502030024-e5908800b52b 6 | ## explicit 7 | golang.org/x/net/websocket 8 | -------------------------------------------------------------------------------- /www/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /www/demos/img/shields-io-signaling.svg: -------------------------------------------------------------------------------- 1 | Stars27 -------------------------------------------------------------------------------- /www/demos/img/tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossrs/signaling/6d8216b947658ab70520d02c5fd2c2054591bf1a/www/demos/img/tooltip.png -------------------------------------------------------------------------------- /www/demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SRS 5 | 6 | 7 | 8 |

Signaling works!

9 |

10 | Run demo for WebRTC: One to One over SFU(SRS)
11 | 点击进入SRS一对一通话演示 12 |

13 |

14 | Run demo for WebRTC: Video Room over SFU(SRS)
15 | 点击进入SRS多人通话演示 16 |

17 | 34 | 35 | -------------------------------------------------------------------------------- /www/demos/js/srs.sdk.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2013-2021 Winlin 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | 'use strict'; 26 | 27 | // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter 28 | // Async-awat-prmise based SRS RTC Publisher. 29 | function SrsRtcPublisherAsync() { 30 | var self = {}; 31 | 32 | // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 33 | self.constraints = { 34 | audio: true, 35 | video: { 36 | width: {ideal: 320, max: 576} 37 | } 38 | }; 39 | 40 | // @see https://github.com/rtcdn/rtcdn-draft 41 | // @url The WebRTC url to play with, for example: 42 | // webrtc://r.ossrs.net/live/livestream 43 | // or specifies the API port: 44 | // webrtc://r.ossrs.net:11985/live/livestream 45 | // or autostart the publish: 46 | // webrtc://r.ossrs.net/live/livestream?autostart=true 47 | // or change the app from live to myapp: 48 | // webrtc://r.ossrs.net:11985/myapp/livestream 49 | // or change the stream from livestream to mystream: 50 | // webrtc://r.ossrs.net:11985/live/mystream 51 | // or set the api server to myapi.domain.com: 52 | // webrtc://myapi.domain.com/live/livestream 53 | // or set the candidate(ip) of answer: 54 | // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 55 | // or force to access https API: 56 | // webrtc://r.ossrs.net/live/livestream?schema=https 57 | // or use plaintext, without SRTP: 58 | // webrtc://r.ossrs.net/live/livestream?encrypt=false 59 | // or any other information, will pass-by in the query: 60 | // webrtc://r.ossrs.net/live/livestream?vhost=xxx 61 | // webrtc://r.ossrs.net/live/livestream?token=xxx 62 | self.publish = async function (url) { 63 | var conf = self.__internal.prepareUrl(url); 64 | self.pc.addTransceiver("audio", {direction: "sendonly"}); 65 | self.pc.addTransceiver("video", {direction: "sendonly"}); 66 | 67 | var stream = await navigator.mediaDevices.getUserMedia(self.constraints); 68 | 69 | // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 70 | stream.getTracks().forEach(function (track) { 71 | self.pc.addTrack(track); 72 | 73 | // Notify about local track when stream is ok. 74 | self.ontrack && self.ontrack({track: track}); 75 | }); 76 | 77 | var offer = await self.pc.createOffer(); 78 | await self.pc.setLocalDescription(offer); 79 | var session = await new Promise(function (resolve, reject) { 80 | // @see https://github.com/rtcdn/rtcdn-draft 81 | var data = { 82 | api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, 83 | clientip: null, sdp: offer.sdp 84 | }; 85 | console.log("Generated offer: ", data); 86 | 87 | $.ajax({ 88 | type: "POST", url: conf.apiUrl, data: JSON.stringify(data), 89 | contentType: 'application/json', dataType: 'json' 90 | }).done(function (data) { 91 | console.log("Got answer: ", data); 92 | if (data.code) { 93 | reject(data); 94 | return; 95 | } 96 | 97 | resolve(data); 98 | }).fail(function (reason) { 99 | reject(reason); 100 | }); 101 | }); 102 | await self.pc.setRemoteDescription( 103 | new RTCSessionDescription({type: 'answer', sdp: session.sdp}) 104 | ); 105 | session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; 106 | 107 | return session; 108 | }; 109 | 110 | // Close the publisher. 111 | self.close = function () { 112 | self.pc && self.pc.close(); 113 | self.pc = null; 114 | }; 115 | 116 | // The callback when got local stream. 117 | // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 118 | self.ontrack = function (event) { 119 | // Add track to stream of SDK. 120 | self.stream.addTrack(event.track); 121 | }; 122 | 123 | // Internal APIs. 124 | self.__internal = { 125 | defaultPath: '/rtc/v1/publish/', 126 | prepareUrl: function (webrtcUrl) { 127 | var urlObject = self.__internal.parse(webrtcUrl); 128 | 129 | // If user specifies the schema, use it as API schema. 130 | var schema = urlObject.user_query.schema; 131 | schema = schema ? schema + ':' : window.location.protocol; 132 | 133 | var port = urlObject.port || 1985; 134 | if (schema === 'https:') { 135 | port = urlObject.port || 443; 136 | } 137 | 138 | // @see https://github.com/rtcdn/rtcdn-draft 139 | var api = urlObject.user_query.play || self.__internal.defaultPath; 140 | if (api.lastIndexOf('/') !== api.length - 1) { 141 | api += '/'; 142 | } 143 | 144 | apiUrl = schema + '//' + urlObject.server + ':' + port + api; 145 | for (var key in urlObject.user_query) { 146 | if (key !== 'api' && key !== 'play') { 147 | apiUrl += '&' + key + '=' + urlObject.user_query[key]; 148 | } 149 | } 150 | // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v 151 | var apiUrl = apiUrl.replace(api + '&', api + '?'); 152 | 153 | var streamUrl = urlObject.url; 154 | 155 | return { 156 | apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, 157 | tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) 158 | }; 159 | }, 160 | parse: function (url) { 161 | // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri 162 | var a = document.createElement("a"); 163 | a.href = url.replace("rtmp://", "http://") 164 | .replace("webrtc://", "http://") 165 | .replace("rtc://", "http://"); 166 | 167 | var vhost = a.hostname; 168 | var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); 169 | var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); 170 | 171 | // parse the vhost in the params of app, that srs supports. 172 | app = app.replace("...vhost...", "?vhost="); 173 | if (app.indexOf("?") >= 0) { 174 | var params = app.slice(app.indexOf("?")); 175 | app = app.slice(0, app.indexOf("?")); 176 | 177 | if (params.indexOf("vhost=") > 0) { 178 | vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); 179 | if (vhost.indexOf("&") > 0) { 180 | vhost = vhost.slice(0, vhost.indexOf("&")); 181 | } 182 | } 183 | } 184 | 185 | // when vhost equals to server, and server is ip, 186 | // the vhost is __defaultVhost__ 187 | if (a.hostname === vhost) { 188 | var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 189 | if (re.test(a.hostname)) { 190 | vhost = "__defaultVhost__"; 191 | } 192 | } 193 | 194 | // parse the schema 195 | var schema = "rtmp"; 196 | if (url.indexOf("://") > 0) { 197 | schema = url.slice(0, url.indexOf("://")); 198 | } 199 | 200 | var port = a.port; 201 | if (!port) { 202 | if (schema === 'http') { 203 | port = 80; 204 | } else if (schema === 'https') { 205 | port = 443; 206 | } else if (schema === 'rtmp') { 207 | port = 1935; 208 | } 209 | } 210 | 211 | var ret = { 212 | url: url, 213 | schema: schema, 214 | server: a.hostname, port: port, 215 | vhost: vhost, app: app, stream: stream 216 | }; 217 | self.__internal.fill_query(a.search, ret); 218 | 219 | // For webrtc API, we use 443 if page is https, or schema specified it. 220 | if (!ret.port) { 221 | if (schema === 'webrtc' || schema === 'rtc') { 222 | if (ret.user_query.schema === 'https') { 223 | ret.port = 443; 224 | } else if (window.location.href.indexOf('https://') === 0) { 225 | ret.port = 443; 226 | } else { 227 | // For WebRTC, SRS use 1985 as default API port. 228 | ret.port = 1985; 229 | } 230 | } 231 | } 232 | 233 | return ret; 234 | }, 235 | fill_query: function (query_string, obj) { 236 | // pure user query object. 237 | obj.user_query = {}; 238 | 239 | if (query_string.length === 0) { 240 | return; 241 | } 242 | 243 | // split again for angularjs. 244 | if (query_string.indexOf("?") >= 0) { 245 | query_string = query_string.split("?")[1]; 246 | } 247 | 248 | var queries = query_string.split("&"); 249 | for (var i = 0; i < queries.length; i++) { 250 | var elem = queries[i]; 251 | 252 | var query = elem.split("="); 253 | obj[query[0]] = query[1]; 254 | obj.user_query[query[0]] = query[1]; 255 | } 256 | 257 | // alias domain for vhost. 258 | if (obj.domain) { 259 | obj.vhost = obj.domain; 260 | } 261 | } 262 | }; 263 | 264 | self.pc = new RTCPeerConnection(null); 265 | 266 | // To keep api consistent between player and publisher. 267 | // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack 268 | // @see https://webrtc.org/getting-started/media-devices 269 | self.stream = new MediaStream(); 270 | 271 | return self; 272 | } 273 | 274 | // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter 275 | // Async-await-promise based SRS RTC Player. 276 | function SrsRtcPlayerAsync() { 277 | var self = {}; 278 | 279 | // @see https://github.com/rtcdn/rtcdn-draft 280 | // @url The WebRTC url to play with, for example: 281 | // webrtc://r.ossrs.net/live/livestream 282 | // or specifies the API port: 283 | // webrtc://r.ossrs.net:11985/live/livestream 284 | // or autostart the play: 285 | // webrtc://r.ossrs.net/live/livestream?autostart=true 286 | // or change the app from live to myapp: 287 | // webrtc://r.ossrs.net:11985/myapp/livestream 288 | // or change the stream from livestream to mystream: 289 | // webrtc://r.ossrs.net:11985/live/mystream 290 | // or set the api server to myapi.domain.com: 291 | // webrtc://myapi.domain.com/live/livestream 292 | // or set the candidate(ip) of answer: 293 | // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 294 | // or force to access https API: 295 | // webrtc://r.ossrs.net/live/livestream?schema=https 296 | // or use plaintext, without SRTP: 297 | // webrtc://r.ossrs.net/live/livestream?encrypt=false 298 | // or any other information, will pass-by in the query: 299 | // webrtc://r.ossrs.net/live/livestream?vhost=xxx 300 | // webrtc://r.ossrs.net/live/livestream?token=xxx 301 | self.play = async function(url) { 302 | var conf = self.__internal.prepareUrl(url); 303 | self.pc.addTransceiver("audio", {direction: "recvonly"}); 304 | self.pc.addTransceiver("video", {direction: "recvonly"}); 305 | 306 | var offer = await self.pc.createOffer(); 307 | await self.pc.setLocalDescription(offer); 308 | var session = await new Promise(function(resolve, reject) { 309 | // @see https://github.com/rtcdn/rtcdn-draft 310 | var data = { 311 | api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, 312 | clientip: null, sdp: offer.sdp 313 | }; 314 | console.log("Generated offer: ", data); 315 | 316 | $.ajax({ 317 | type: "POST", url: conf.apiUrl, data: JSON.stringify(data), 318 | contentType:'application/json', dataType: 'json' 319 | }).done(function(data) { 320 | console.log("Got answer: ", data); 321 | if (data.code) { 322 | reject(data); return; 323 | } 324 | 325 | resolve(data); 326 | }).fail(function(reason){ 327 | reject(reason); 328 | }); 329 | }); 330 | await self.pc.setRemoteDescription( 331 | new RTCSessionDescription({type: 'answer', sdp: session.sdp}) 332 | ); 333 | session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; 334 | return session; 335 | }; 336 | 337 | // Close the player. 338 | self.close = function() { 339 | self.pc && self.pc.close(); 340 | self.pc = null; 341 | }; 342 | 343 | // The callback when got remote track. 344 | // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream 345 | self.ontrack = function (event) { 346 | // https://webrtc.org/getting-started/remote-streams 347 | self.stream.addTrack(event.track); 348 | }; 349 | 350 | // Internal APIs. 351 | self.__internal = { 352 | defaultPath: '/rtc/v1/play/', 353 | prepareUrl: function (webrtcUrl) { 354 | var urlObject = self.__internal.parse(webrtcUrl); 355 | 356 | // If user specifies the schema, use it as API schema. 357 | var schema = urlObject.user_query.schema; 358 | schema = schema ? schema + ':' : window.location.protocol; 359 | 360 | var port = urlObject.port || 1985; 361 | if (schema === 'https:') { 362 | port = urlObject.port || 443; 363 | } 364 | 365 | // @see https://github.com/rtcdn/rtcdn-draft 366 | var api = urlObject.user_query.play || self.__internal.defaultPath; 367 | if (api.lastIndexOf('/') !== api.length - 1) { 368 | api += '/'; 369 | } 370 | 371 | apiUrl = schema + '//' + urlObject.server + ':' + port + api; 372 | for (var key in urlObject.user_query) { 373 | if (key !== 'api' && key !== 'play') { 374 | apiUrl += '&' + key + '=' + urlObject.user_query[key]; 375 | } 376 | } 377 | // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v 378 | var apiUrl = apiUrl.replace(api + '&', api + '?'); 379 | 380 | var streamUrl = urlObject.url; 381 | 382 | return { 383 | apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, 384 | tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) 385 | }; 386 | }, 387 | parse: function (url) { 388 | // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri 389 | var a = document.createElement("a"); 390 | a.href = url.replace("rtmp://", "http://") 391 | .replace("webrtc://", "http://") 392 | .replace("rtc://", "http://"); 393 | 394 | var vhost = a.hostname; 395 | var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); 396 | var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); 397 | 398 | // parse the vhost in the params of app, that srs supports. 399 | app = app.replace("...vhost...", "?vhost="); 400 | if (app.indexOf("?") >= 0) { 401 | var params = app.slice(app.indexOf("?")); 402 | app = app.slice(0, app.indexOf("?")); 403 | 404 | if (params.indexOf("vhost=") > 0) { 405 | vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); 406 | if (vhost.indexOf("&") > 0) { 407 | vhost = vhost.slice(0, vhost.indexOf("&")); 408 | } 409 | } 410 | } 411 | 412 | // when vhost equals to server, and server is ip, 413 | // the vhost is __defaultVhost__ 414 | if (a.hostname === vhost) { 415 | var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 416 | if (re.test(a.hostname)) { 417 | vhost = "__defaultVhost__"; 418 | } 419 | } 420 | 421 | // parse the schema 422 | var schema = "rtmp"; 423 | if (url.indexOf("://") > 0) { 424 | schema = url.slice(0, url.indexOf("://")); 425 | } 426 | 427 | var port = a.port; 428 | if (!port) { 429 | if (schema === 'http') { 430 | port = 80; 431 | } else if (schema === 'https') { 432 | port = 443; 433 | } else if (schema === 'rtmp') { 434 | port = 1935; 435 | } 436 | } 437 | 438 | var ret = { 439 | url: url, 440 | schema: schema, 441 | server: a.hostname, port: port, 442 | vhost: vhost, app: app, stream: stream 443 | }; 444 | self.__internal.fill_query(a.search, ret); 445 | 446 | // For webrtc API, we use 443 if page is https, or schema specified it. 447 | if (!ret.port) { 448 | if (schema === 'webrtc' || schema === 'rtc') { 449 | if (ret.user_query.schema === 'https') { 450 | ret.port = 443; 451 | } else if (window.location.href.indexOf('https://') === 0) { 452 | ret.port = 443; 453 | } else { 454 | // For WebRTC, SRS use 1985 as default API port. 455 | ret.port = 1985; 456 | } 457 | } 458 | } 459 | 460 | return ret; 461 | }, 462 | fill_query: function (query_string, obj) { 463 | // pure user query object. 464 | obj.user_query = {}; 465 | 466 | if (query_string.length === 0) { 467 | return; 468 | } 469 | 470 | // split again for angularjs. 471 | if (query_string.indexOf("?") >= 0) { 472 | query_string = query_string.split("?")[1]; 473 | } 474 | 475 | var queries = query_string.split("&"); 476 | for (var i = 0; i < queries.length; i++) { 477 | var elem = queries[i]; 478 | 479 | var query = elem.split("="); 480 | obj[query[0]] = query[1]; 481 | obj.user_query[query[0]] = query[1]; 482 | } 483 | 484 | // alias domain for vhost. 485 | if (obj.domain) { 486 | obj.vhost = obj.domain; 487 | } 488 | } 489 | }; 490 | 491 | self.pc = new RTCPeerConnection(null); 492 | 493 | // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams 494 | self.stream = new MediaStream(); 495 | 496 | // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack 497 | self.pc.ontrack = function(event) { 498 | if (self.ontrack) { 499 | self.ontrack(event); 500 | } 501 | }; 502 | 503 | return self; 504 | } 505 | 506 | // Format the codec of RTCRtpSender, kind(audio/video) is optional filter. 507 | // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs 508 | function SrsRtcFormatSenders(senders, kind) { 509 | var codecs = []; 510 | senders.forEach(function (sender) { 511 | var params = sender.getParameters(); 512 | params && params.codecs && params.codecs.forEach(function(c) { 513 | if (kind && sender.track.kind !== kind) { 514 | return; 515 | } 516 | 517 | if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) { 518 | return; 519 | } 520 | 521 | var s = ''; 522 | 523 | s += c.mimeType.replace('audio/', '').replace('video/', ''); 524 | s += ', ' + c.clockRate + 'HZ'; 525 | if (sender.track.kind === "audio") { 526 | s += ', channels: ' + c.channels; 527 | } 528 | s += ', pt: ' + c.payloadType; 529 | 530 | codecs.push(s); 531 | }); 532 | }); 533 | return codecs.join(", "); 534 | } 535 | 536 | -------------------------------------------------------------------------------- /www/demos/js/srs.sig.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2013-2021 Winlin 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | 'use strict'; 26 | 27 | // Async-await-promise based SRS RTC Signaling. 28 | function SrsRtcSignalingAsync() { 29 | var self = {}; 30 | 31 | // The schema is ws or wss, host is ip or ip:port, display is nickname 32 | // of user to join the room. 33 | self.connect = async function (schema, host, room, display) { 34 | var url = schema + '://' + host + '/sig/v1/rtc'; 35 | self.ws = new WebSocket(url + '?room=' + room + '&display=' + display); 36 | 37 | self.ws.onmessage = function(event) { 38 | var r = JSON.parse(event.data); 39 | var promise = self._internals.msgs[r.tid]; 40 | if (promise) { 41 | promise.resolve(r.msg); 42 | delete self._internals.msgs[r.tid]; 43 | } else { 44 | self.onmessage(r.msg); 45 | } 46 | }; 47 | 48 | return new Promise(function (resolve, reject) { 49 | self.ws.onopen = function (event) { 50 | resolve(event); 51 | }; 52 | 53 | self.ws.onerror = function (event) { 54 | reject(event); 55 | }; 56 | }); 57 | }; 58 | 59 | // The message is a json object. 60 | self.send = async function (message) { 61 | return new Promise(function (resolve, reject) { 62 | var r = {tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7), msg: message}; 63 | self._internals.msgs[r.tid] = {resolve: resolve, reject: reject}; 64 | self.ws.send(JSON.stringify(r)); 65 | }); 66 | }; 67 | 68 | self.close = function () { 69 | self.ws && self.ws.close(); 70 | self.ws = null; 71 | 72 | for (const tid in self._internals.msgs) { 73 | var promise = self._internals.msgs[tid]; 74 | promise.reject('close'); 75 | } 76 | }; 77 | 78 | // The callback when got messages from signaling server. 79 | self.onmessage = function (msg) { 80 | }; 81 | 82 | self._internals = { 83 | // Key is tid, value is object {resolve, reject, response}. 84 | msgs: {} 85 | }; 86 | 87 | return self; 88 | } 89 | 90 | // Parse params in query string. 91 | function SrsRtcSignalingParse(location) { 92 | let query = location.href.split('?')[1]; 93 | query = query? '?' + query : null; 94 | 95 | let wsSchema = location.href.split('wss=')[1]; 96 | wsSchema = wsSchema? wsSchema.split('&')[0] : (location.protocol === 'http:'? 'ws' : 'wss'); 97 | 98 | let wsHost = location.href.split('wsh=')[1]; 99 | wsHost = wsHost? wsHost.split('&')[0] : location.hostname; 100 | 101 | let wsPort = location.href.split('wsp=')[1]; 102 | wsPort = wsPort? wsPort.split('&')[0] : location.host.split(':')[1]; 103 | 104 | let host = location.href.split('host=')[1]; 105 | host = host? host.split('&')[0] : location.hostname; 106 | 107 | let room = location.href.split('room=')[1]; 108 | room = room? room.split('&')[0] : null; 109 | 110 | let display = location.href.split('display=')[1]; 111 | display = display? display.split('&')[0] : Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).toString(16).slice(0, 7); 112 | 113 | let autostart = location.href.split('autostart=')[1]; 114 | autostart = autostart && autostart.split('&')[0] === 'true'; 115 | 116 | // Remove data in query. 117 | let rawQuery = query; 118 | if (query) { 119 | query = query.replace('wss=' + wsSchema, ''); 120 | query = query.replace('wsh=' + wsHost, ''); 121 | query = query.replace('wsp=' + wsPort, ''); 122 | query = query.replace('host=' + host, ''); 123 | if (room) { 124 | query = query.replace('room=' + room, ''); 125 | } 126 | query = query.replace('display=' + display, ''); 127 | query = query.replace('autostart=' + autostart, ''); 128 | 129 | while (query.indexOf('&&') >= 0) { 130 | query = query.replace('&&', '&'); 131 | } 132 | query = query.replace('?&', '?'); 133 | if (query.lastIndexOf('?') === query.length - 1) { 134 | query = query.slice(0, query.length - 1); 135 | } 136 | if (query.lastIndexOf('&') === query.length - 1) { 137 | query = query.slice(0, query.length - 1); 138 | } 139 | } 140 | 141 | // Regenerate the host of websocket. 142 | wsHost = wsPort? wsHost.split(':')[0] + ':' + wsPort : wsHost; 143 | 144 | return { 145 | query: query, rawQuery: rawQuery, wsSchema: wsSchema, wsHost: wsHost, host: host, 146 | room: room, display: display, autostart: autostart, 147 | }; 148 | } 149 | -------------------------------------------------------------------------------- /www/demos/one2one.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SRS 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 37 |
38 |
39 | SRS: 40 | 41 | Room: 42 | 43 | Display: 44 | 45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | Refresh 63 | 64 | Alert 65 |
66 |
67 | 68 | 69 | 70 |
71 |
72 | 75 |
76 |
77 | ffmpeg -f flv -i rtmp://// -f flv -i rtmp://// \
78 |      -filter_complex "[1:v]scale=w=96:h=72[ckout];[0:v][ckout]overlay=x=W-w-10:y=H-h-10[out]" -map "[out]" \
79 |      -c:v libx264 -profile:v high -preset medium \
80 |      -filter_complex amix -c:a aac \
81 |      -f flv -y 82 | 83 | rtmp:////merge 84 | 85 | 86 | &&
87 | echo "ok" 88 |
89 | 94 |
95 |
96 |
97 | 98 | 99 | 100 |
101 |
102 | 107 |
108 |
109 | 推流地址 110 |
111 |
112 | 推流密钥 113 |
114 |
115 | 116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |

© SRS 2020

124 |
125 |
126 | 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /www/demos/room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SRS 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 37 |
38 |
39 | SRS: 40 | 41 | Room: 42 | 43 | Display: 44 | 45 | 46 |
47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossrs/signaling/6d8216b947658ab70520d02c5fd2c2054591bf1a/www/favicon.ico -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SRS 5 | 6 | 9 | 10 | --------------------------------------------------------------------------------