Added clean exec functionality + ACI implementation - V2 (#244)
* Stubs and vkubelet changes * added dependencies * Azure provider exec implementation * added missing dependencies * added vkubelet imports * added huawei exec stub * Fixed exec tab functionality / stdin buffer length * Removed unused import * Added provider function GetPodFullName + ACI implementation * Added error handling in ACI provider exec
This commit is contained in:
@@ -2,6 +2,7 @@ package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// FargateProvider implements the virtual-kubelet provider interface.
|
||||
@@ -175,6 +178,19 @@ func (p *FargateProvider) GetContainerLogs(namespace, podName, containerName str
|
||||
return p.cluster.GetContainerLogs(namespace, podName, containerName, tail)
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
func (p *FargateProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
// TODO: Implementation
|
||||
func (p *FargateProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus retrieves the status of a pod by name from the provider.
|
||||
func (p *FargateProvider) GetPodStatus(namespace, name string) (*corev1.PodStatus, error) {
|
||||
log.Printf("Received GetPodStatus request for %s/%s.\n", namespace, name)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
client "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// The service account secret mount path.
|
||||
@@ -188,7 +191,7 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
if podsQuota := os.Getenv("ACI_QUOTA_POD"); podsQuota != "" {
|
||||
p.pods = podsQuota
|
||||
}
|
||||
|
||||
|
||||
p.operatingSystem = operatingSystem
|
||||
p.nodeName = nodeName
|
||||
p.internalIP = internalIP
|
||||
@@ -329,6 +332,88 @@ func (p *ACIProvider) GetContainerLogs(namespace, podName, containerName string,
|
||||
return logContent, err
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
func (p *ACIProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return fmt.Sprintf("%s-%s", namespace, pod)
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
func (p *ACIProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, errstream io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
// Cleanup on exit
|
||||
if out != nil {
|
||||
defer out.Close()
|
||||
}
|
||||
if errstream != nil {
|
||||
defer errstream.Close()
|
||||
}
|
||||
|
||||
cg, err, _ := p.aciClient.GetContainerGroup(p.resourceGroup, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set default terminal size
|
||||
terminalSize := remotecommand.TerminalSize{
|
||||
Height: 60,
|
||||
Width: 120,
|
||||
}
|
||||
|
||||
if resize != nil {
|
||||
terminalSize = <-resize // Receive terminal resize event if resize stream is present
|
||||
}
|
||||
|
||||
xcrsp, err := p.aciClient.LaunchExec(p.resourceGroup, cg.Name, container, cmd[0], terminalSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wsUri := xcrsp.WebSocketUri
|
||||
password := xcrsp.Password
|
||||
|
||||
c, _, _ := websocket.DefaultDialer.Dial(wsUri, nil)
|
||||
c.WriteMessage(websocket.TextMessage, []byte(password)) // Websocket password needs to be sent before WS terminal is active
|
||||
|
||||
// Cleanup on exit
|
||||
defer c.Close()
|
||||
defer out.Close()
|
||||
|
||||
if in != nil {
|
||||
// Inputstream
|
||||
go func() {
|
||||
for {
|
||||
var msg = make([]byte, 512)
|
||||
n, err := in.Read(msg)
|
||||
if err == io.EOF {
|
||||
// Handle EOF
|
||||
}
|
||||
if err != nil {
|
||||
// Handle errors
|
||||
return
|
||||
}
|
||||
if n > 0 { // Only call WriteMessage if there is data to send
|
||||
c.WriteMessage(websocket.BinaryMessage, msg[:n])
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
//Outputstream
|
||||
for {
|
||||
_, cr, err := c.NextReader()
|
||||
if err != nil {
|
||||
// Handle errors
|
||||
break
|
||||
}
|
||||
io.Copy(out, cr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is running inside ACI
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *ACIProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
containerGroupListURLPath = "subscriptions/{{.subscriptionId}}/providers/Microsoft.ContainerInstance/containerGroups"
|
||||
containerGroupListByResourceGroupURLPath = "subscriptions/{{.subscriptionId}}/resourceGroups/{{.resourceGroup}}/providers/Microsoft.ContainerInstance/containerGroups"
|
||||
containerLogsURLPath = containerGroupURLPath + "/containers/{{.containerName}}/logs"
|
||||
containerExecURLPath = containerGroupURLPath + "/containers/{{.containerName}}/exec"
|
||||
)
|
||||
|
||||
// Client is a client for interacting with Azure Container Instances.
|
||||
|
||||
84
providers/azure/client/aci/exec.go
Normal file
84
providers/azure/client/aci/exec.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package aci
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/api"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
type TerminalSizeRequest struct {
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
// Starts the exec command for a specified container instance in a specified resource group and container group.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/startcontainer/launchexec
|
||||
func (c *Client) LaunchExec(resourceGroup, containerGroupName, containerName, command string, terminalSize remotecommand.TerminalSize) (ExecResponse, error) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
|
||||
// Create the url to call Azure REST API
|
||||
uri := api.ResolveRelative(baseURI, containerExecURLPath)
|
||||
uri += "?" + url.Values(urlParams).Encode()
|
||||
|
||||
var xc ExecRequest
|
||||
|
||||
xc.Command = command
|
||||
xc.TerminalSize.Rows = int(terminalSize.Height)
|
||||
xc.TerminalSize.Cols = int(terminalSize.Width)
|
||||
|
||||
var xcrsp ExecResponse
|
||||
xcrsp.Password = ""
|
||||
xcrsp.WebSocketUri = ""
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
if err := json.NewEncoder(b).Encode(xc); err != nil {
|
||||
return xcrsp, fmt.Errorf("Encoding create launch exec body request failed: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", uri, b)
|
||||
if err != nil {
|
||||
return xcrsp, fmt.Errorf("Creating launch exec uri request failed: %v", err)
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
if err := api.ExpandURL(req.URL, map[string]string{
|
||||
"subscriptionId": c.auth.SubscriptionID,
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
"containerName": containerName,
|
||||
}); err != nil {
|
||||
return xcrsp, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return xcrsp, fmt.Errorf("Sending launch exec request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return xcrsp, err
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return xcrsp, errors.New("Create launch exec returned an empty body in the response")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&xcrsp); err != nil {
|
||||
return xcrsp, fmt.Errorf("Decoding create launch exec response body failed: %v", err)
|
||||
}
|
||||
|
||||
return xcrsp, nil
|
||||
}
|
||||
@@ -275,3 +275,21 @@ type VolumeMount struct {
|
||||
MountPath string `json:"mountPath,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
}
|
||||
|
||||
// TerminalSize is the size of the Launch Exec terminal
|
||||
type TerminalSize struct {
|
||||
Rows int `json:"rows,omitempty"`
|
||||
Cols int `json:"cols,omitempty"`
|
||||
}
|
||||
|
||||
// ExecRequest is a request for Launch Exec API response for ACI.
|
||||
type ExecRequest struct {
|
||||
Command string `json:"command,omitempty"`
|
||||
TerminalSize TerminalSize `json:"terminalSize,omitempty"`
|
||||
}
|
||||
|
||||
// ExecRequest is a request for Launch Exec API response for ACI.
|
||||
type ExecResponse struct {
|
||||
WebSocketUri string `json:"webSocketUri,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
|
||||
@@ -22,6 +23,8 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -300,6 +303,20 @@ func (p *Provider) GetContainerLogs(namespace, podName, containerName string, ta
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *Provider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
// TODO: Implementation
|
||||
func (p *Provider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPods retrieves a list of all pods scheduled to run.
|
||||
func (p *Provider) GetPods() ([]*v1.Pod, error) {
|
||||
log.Println("Getting pods...")
|
||||
|
||||
@@ -3,6 +3,7 @@ package cri
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
criapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||
)
|
||||
|
||||
@@ -642,6 +644,20 @@ func (p *CRIProvider) GetContainerLogs(namespace, podName, containerName string,
|
||||
return readLogFile(container.LogPath, tail)
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *CRIProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
// TODO: Implementation
|
||||
func (p *CRIProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find a pod by name and namespace. Pods are indexed by UID
|
||||
func (p *CRIProvider) findPodByName(namespace, name string) *CRIPod {
|
||||
var found *CRIPod
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -278,6 +280,20 @@ func (p *CCIProvider) GetContainerLogs(namespace, podName, containerName string,
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *CCIProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
// TODO: Implementation
|
||||
func (p *CCIProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus retrieves the status of a pod by name from the huawei CCI provider.
|
||||
func (p *CCIProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
pod, err := p.GetPod(namespace, name)
|
||||
|
||||
@@ -3,10 +3,12 @@ package hypersh
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
@@ -21,6 +23,8 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
var host = "tcp://*.hyper.sh:443"
|
||||
@@ -318,6 +322,20 @@ func (p *HyperProvider) GetContainerLogs(namespace, podName, containerName strin
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *HyperProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
// TODO: Implementation
|
||||
func (p *HyperProvider) ExecInContainer(name string, uid apitypes.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is running inside hyper.sh
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *HyperProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package mock
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -148,6 +151,19 @@ func (p *MockProvider) GetContainerLogs(namespace, podName, containerName string
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *MockProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
func (p *MockProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is "running".
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *MockProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package vic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
@@ -33,6 +34,8 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
type VicProvider struct {
|
||||
@@ -258,6 +261,19 @@ func (v *VicProvider) GetContainerLogs(namespace, podName, containerName string,
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *VicProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
func (p *VicProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus retrieves the status of a pod by name from the provider.
|
||||
// This function needs to return a status or the reconcile loop will stop running.
|
||||
func (v *VicProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -28,6 +29,8 @@ import (
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// BrokerProvider implements the virtual-kubelet provider interface by forwarding kubelet calls to a web endpoint.
|
||||
@@ -122,6 +125,20 @@ func (p *BrokerProvider) GetContainerLogs(namespace, podName, containerName stri
|
||||
return string(response), nil
|
||||
}
|
||||
|
||||
// Get full pod name as defined in the provider context
|
||||
// TODO: Implementation
|
||||
func (p *BrokerProvider) GetPodFullName(namespace string, pod string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
// TODO: Implementation
|
||||
func (p *BrokerProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
log.Printf("receive ExecInContainer %q\n", container)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodStatus retrieves the status of a given pod by name.
|
||||
func (p *BrokerProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
urlPathStr := fmt.Sprintf(
|
||||
|
||||
191
vendor/github.com/docker/spdystream/LICENSE
generated
vendored
Normal file
191
vendor/github.com/docker/spdystream/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2015 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
425
vendor/github.com/docker/spdystream/LICENSE.docs
generated
vendored
Normal file
425
vendor/github.com/docker/spdystream/LICENSE.docs
generated
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
Attribution-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International Public
|
||||
License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-ShareAlike 4.0 International Public License ("Public
|
||||
License"). To the extent this Public License may be interpreted as a
|
||||
contract, You are granted the Licensed Rights in consideration of Your
|
||||
acceptance of these terms and conditions, and the Licensor grants You
|
||||
such rights in consideration of benefits the Licensor receives from
|
||||
making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
l. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
m. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
|
||||
including for purposes of Section 3(b); and
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public licenses.
|
||||
Notwithstanding, Creative Commons may elect to apply one of its public
|
||||
licenses to material it publishes and in those instances will be
|
||||
considered the "Licensor." Except for the limited purpose of indicating
|
||||
that material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the public
|
||||
licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
959
vendor/github.com/docker/spdystream/connection.go
generated
vendored
Normal file
959
vendor/github.com/docker/spdystream/connection.go
generated
vendored
Normal file
@@ -0,0 +1,959 @@
|
||||
package spdystream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidStreamId = errors.New("Invalid stream id")
|
||||
ErrTimeout = errors.New("Timeout occurred")
|
||||
ErrReset = errors.New("Stream reset")
|
||||
ErrWriteClosedStream = errors.New("Write on closed stream")
|
||||
)
|
||||
|
||||
const (
|
||||
FRAME_WORKERS = 5
|
||||
QUEUE_SIZE = 50
|
||||
)
|
||||
|
||||
type StreamHandler func(stream *Stream)
|
||||
|
||||
type AuthHandler func(header http.Header, slot uint8, parent uint32) bool
|
||||
|
||||
type idleAwareFramer struct {
|
||||
f *spdy.Framer
|
||||
conn *Connection
|
||||
writeLock sync.Mutex
|
||||
resetChan chan struct{}
|
||||
setTimeoutLock sync.Mutex
|
||||
setTimeoutChan chan time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newIdleAwareFramer(framer *spdy.Framer) *idleAwareFramer {
|
||||
iaf := &idleAwareFramer{
|
||||
f: framer,
|
||||
resetChan: make(chan struct{}, 2),
|
||||
// setTimeoutChan needs to be buffered to avoid deadlocks when calling setIdleTimeout at about
|
||||
// the same time the connection is being closed
|
||||
setTimeoutChan: make(chan time.Duration, 1),
|
||||
}
|
||||
return iaf
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) monitor() {
|
||||
var (
|
||||
timer *time.Timer
|
||||
expired <-chan time.Time
|
||||
resetChan = i.resetChan
|
||||
setTimeoutChan = i.setTimeoutChan
|
||||
)
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case timeout := <-i.setTimeoutChan:
|
||||
i.timeout = timeout
|
||||
if timeout == 0 {
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
} else {
|
||||
if timer == nil {
|
||||
timer = time.NewTimer(timeout)
|
||||
expired = timer.C
|
||||
} else {
|
||||
timer.Reset(timeout)
|
||||
}
|
||||
}
|
||||
case <-resetChan:
|
||||
if timer != nil && i.timeout > 0 {
|
||||
timer.Reset(i.timeout)
|
||||
}
|
||||
case <-expired:
|
||||
i.conn.streamCond.L.Lock()
|
||||
streams := i.conn.streams
|
||||
i.conn.streams = make(map[spdy.StreamId]*Stream)
|
||||
i.conn.streamCond.Broadcast()
|
||||
i.conn.streamCond.L.Unlock()
|
||||
go func() {
|
||||
for _, stream := range streams {
|
||||
stream.resetStream()
|
||||
}
|
||||
i.conn.Close()
|
||||
}()
|
||||
case <-i.conn.closeChan:
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// Start a goroutine to drain resetChan. This is needed because we've seen
|
||||
// some unit tests with large numbers of goroutines get into a situation
|
||||
// where resetChan fills up, at least 1 call to Write() is still trying to
|
||||
// send to resetChan, the connection gets closed, and this case statement
|
||||
// attempts to grab the write lock that Write() already has, causing a
|
||||
// deadlock.
|
||||
//
|
||||
// See https://github.com/docker/spdystream/issues/49 for more details.
|
||||
go func() {
|
||||
for _ = range resetChan {
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for _ = range setTimeoutChan {
|
||||
}
|
||||
}()
|
||||
|
||||
i.writeLock.Lock()
|
||||
close(resetChan)
|
||||
i.resetChan = nil
|
||||
i.writeLock.Unlock()
|
||||
|
||||
i.setTimeoutLock.Lock()
|
||||
close(i.setTimeoutChan)
|
||||
i.setTimeoutChan = nil
|
||||
i.setTimeoutLock.Unlock()
|
||||
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
|
||||
// Drain resetChan
|
||||
for _ = range resetChan {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) WriteFrame(frame spdy.Frame) error {
|
||||
i.writeLock.Lock()
|
||||
defer i.writeLock.Unlock()
|
||||
if i.resetChan == nil {
|
||||
return io.EOF
|
||||
}
|
||||
err := i.f.WriteFrame(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.resetChan <- struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) ReadFrame() (spdy.Frame, error) {
|
||||
frame, err := i.f.ReadFrame()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// resetChan should never be closed since it is only closed
|
||||
// when the connection has closed its closeChan. This closure
|
||||
// only occurs after all Reads have finished
|
||||
// TODO (dmcgowan): refactor relationship into connection
|
||||
i.resetChan <- struct{}{}
|
||||
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) setIdleTimeout(timeout time.Duration) {
|
||||
i.setTimeoutLock.Lock()
|
||||
defer i.setTimeoutLock.Unlock()
|
||||
|
||||
if i.setTimeoutChan == nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.setTimeoutChan <- timeout
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
conn net.Conn
|
||||
framer *idleAwareFramer
|
||||
|
||||
closeChan chan bool
|
||||
goneAway bool
|
||||
lastStreamChan chan<- *Stream
|
||||
goAwayTimeout time.Duration
|
||||
closeTimeout time.Duration
|
||||
|
||||
streamLock *sync.RWMutex
|
||||
streamCond *sync.Cond
|
||||
streams map[spdy.StreamId]*Stream
|
||||
|
||||
nextIdLock sync.Mutex
|
||||
receiveIdLock sync.Mutex
|
||||
nextStreamId spdy.StreamId
|
||||
receivedStreamId spdy.StreamId
|
||||
|
||||
pingIdLock sync.Mutex
|
||||
pingId uint32
|
||||
pingChans map[uint32]chan error
|
||||
|
||||
shutdownLock sync.Mutex
|
||||
shutdownChan chan error
|
||||
hasShutdown bool
|
||||
|
||||
// for testing https://github.com/docker/spdystream/pull/56
|
||||
dataFrameHandler func(*spdy.DataFrame) error
|
||||
}
|
||||
|
||||
// NewConnection creates a new spdy connection from an existing
|
||||
// network connection.
|
||||
func NewConnection(conn net.Conn, server bool) (*Connection, error) {
|
||||
framer, framerErr := spdy.NewFramer(conn, conn)
|
||||
if framerErr != nil {
|
||||
return nil, framerErr
|
||||
}
|
||||
idleAwareFramer := newIdleAwareFramer(framer)
|
||||
var sid spdy.StreamId
|
||||
var rid spdy.StreamId
|
||||
var pid uint32
|
||||
if server {
|
||||
sid = 2
|
||||
rid = 1
|
||||
pid = 2
|
||||
} else {
|
||||
sid = 1
|
||||
rid = 2
|
||||
pid = 1
|
||||
}
|
||||
|
||||
streamLock := new(sync.RWMutex)
|
||||
streamCond := sync.NewCond(streamLock)
|
||||
|
||||
session := &Connection{
|
||||
conn: conn,
|
||||
framer: idleAwareFramer,
|
||||
|
||||
closeChan: make(chan bool),
|
||||
goAwayTimeout: time.Duration(0),
|
||||
closeTimeout: time.Duration(0),
|
||||
|
||||
streamLock: streamLock,
|
||||
streamCond: streamCond,
|
||||
streams: make(map[spdy.StreamId]*Stream),
|
||||
nextStreamId: sid,
|
||||
receivedStreamId: rid,
|
||||
|
||||
pingId: pid,
|
||||
pingChans: make(map[uint32]chan error),
|
||||
|
||||
shutdownChan: make(chan error),
|
||||
}
|
||||
session.dataFrameHandler = session.handleDataFrame
|
||||
idleAwareFramer.conn = session
|
||||
go idleAwareFramer.monitor()
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Ping sends a ping frame across the connection and
|
||||
// returns the response time
|
||||
func (s *Connection) Ping() (time.Duration, error) {
|
||||
pid := s.pingId
|
||||
s.pingIdLock.Lock()
|
||||
if s.pingId > 0x7ffffffe {
|
||||
s.pingId = s.pingId - 0x7ffffffe
|
||||
} else {
|
||||
s.pingId = s.pingId + 2
|
||||
}
|
||||
s.pingIdLock.Unlock()
|
||||
pingChan := make(chan error)
|
||||
s.pingChans[pid] = pingChan
|
||||
defer delete(s.pingChans, pid)
|
||||
|
||||
frame := &spdy.PingFrame{Id: pid}
|
||||
startTime := time.Now()
|
||||
writeErr := s.framer.WriteFrame(frame)
|
||||
if writeErr != nil {
|
||||
return time.Duration(0), writeErr
|
||||
}
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return time.Duration(0), errors.New("connection closed")
|
||||
case err, ok := <-pingChan:
|
||||
if ok && err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
break
|
||||
}
|
||||
return time.Now().Sub(startTime), nil
|
||||
}
|
||||
|
||||
// Serve handles frames sent from the server, including reply frames
|
||||
// which are needed to fully initiate connections. Both clients and servers
|
||||
// should call Serve in a separate goroutine before creating streams.
|
||||
func (s *Connection) Serve(newHandler StreamHandler) {
|
||||
// use a WaitGroup to wait for all frames to be drained after receiving
|
||||
// go-away.
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Parition queues to ensure stream frames are handled
|
||||
// by the same worker, ensuring order is maintained
|
||||
frameQueues := make([]*PriorityFrameQueue, FRAME_WORKERS)
|
||||
for i := 0; i < FRAME_WORKERS; i++ {
|
||||
frameQueues[i] = NewPriorityFrameQueue(QUEUE_SIZE)
|
||||
|
||||
// Ensure frame queue is drained when connection is closed
|
||||
go func(frameQueue *PriorityFrameQueue) {
|
||||
<-s.closeChan
|
||||
frameQueue.Drain()
|
||||
}(frameQueues[i])
|
||||
|
||||
wg.Add(1)
|
||||
go func(frameQueue *PriorityFrameQueue) {
|
||||
// let the WaitGroup know this worker is done
|
||||
defer wg.Done()
|
||||
|
||||
s.frameHandler(frameQueue, newHandler)
|
||||
}(frameQueues[i])
|
||||
}
|
||||
|
||||
var (
|
||||
partitionRoundRobin int
|
||||
goAwayFrame *spdy.GoAwayFrame
|
||||
)
|
||||
Loop:
|
||||
for {
|
||||
readFrame, err := s.framer.ReadFrame()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
debugMessage("frame read error: %s", err)
|
||||
} else {
|
||||
debugMessage("(%p) EOF received", s)
|
||||
}
|
||||
break
|
||||
}
|
||||
var priority uint8
|
||||
var partition int
|
||||
switch frame := readFrame.(type) {
|
||||
case *spdy.SynStreamFrame:
|
||||
if s.checkStreamFrame(frame) {
|
||||
priority = frame.Priority
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
debugMessage("(%p) Add stream frame: %d ", s, frame.StreamId)
|
||||
s.addStreamFrame(frame)
|
||||
} else {
|
||||
debugMessage("(%p) Rejected stream frame: %d ", s, frame.StreamId)
|
||||
continue
|
||||
}
|
||||
case *spdy.SynReplyFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.DataFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.RstStreamFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.HeadersFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.PingFrame:
|
||||
priority = 0
|
||||
partition = partitionRoundRobin
|
||||
partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS
|
||||
case *spdy.GoAwayFrame:
|
||||
// hold on to the go away frame and exit the loop
|
||||
goAwayFrame = frame
|
||||
break Loop
|
||||
default:
|
||||
priority = 7
|
||||
partition = partitionRoundRobin
|
||||
partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS
|
||||
}
|
||||
frameQueues[partition].Push(readFrame, priority)
|
||||
}
|
||||
close(s.closeChan)
|
||||
|
||||
// wait for all frame handler workers to indicate they've drained their queues
|
||||
// before handling the go away frame
|
||||
wg.Wait()
|
||||
|
||||
if goAwayFrame != nil {
|
||||
s.handleGoAwayFrame(goAwayFrame)
|
||||
}
|
||||
|
||||
// now it's safe to close remote channels and empty s.streams
|
||||
s.streamCond.L.Lock()
|
||||
// notify streams that they're now closed, which will
|
||||
// unblock any stream Read() calls
|
||||
for _, stream := range s.streams {
|
||||
stream.closeRemoteChannels()
|
||||
}
|
||||
s.streams = make(map[spdy.StreamId]*Stream)
|
||||
s.streamCond.Broadcast()
|
||||
s.streamCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (s *Connection) frameHandler(frameQueue *PriorityFrameQueue, newHandler StreamHandler) {
|
||||
for {
|
||||
popFrame := frameQueue.Pop()
|
||||
if popFrame == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var frameErr error
|
||||
switch frame := popFrame.(type) {
|
||||
case *spdy.SynStreamFrame:
|
||||
frameErr = s.handleStreamFrame(frame, newHandler)
|
||||
case *spdy.SynReplyFrame:
|
||||
frameErr = s.handleReplyFrame(frame)
|
||||
case *spdy.DataFrame:
|
||||
frameErr = s.dataFrameHandler(frame)
|
||||
case *spdy.RstStreamFrame:
|
||||
frameErr = s.handleResetFrame(frame)
|
||||
case *spdy.HeadersFrame:
|
||||
frameErr = s.handleHeaderFrame(frame)
|
||||
case *spdy.PingFrame:
|
||||
frameErr = s.handlePingFrame(frame)
|
||||
case *spdy.GoAwayFrame:
|
||||
frameErr = s.handleGoAwayFrame(frame)
|
||||
default:
|
||||
frameErr = fmt.Errorf("unhandled frame type: %T", frame)
|
||||
}
|
||||
|
||||
if frameErr != nil {
|
||||
debugMessage("frame handling error: %s", frameErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Connection) getStreamPriority(streamId spdy.StreamId) uint8 {
|
||||
stream, streamOk := s.getStream(streamId)
|
||||
if !streamOk {
|
||||
return 7
|
||||
}
|
||||
return stream.priority
|
||||
}
|
||||
|
||||
func (s *Connection) addStreamFrame(frame *spdy.SynStreamFrame) {
|
||||
var parent *Stream
|
||||
if frame.AssociatedToStreamId != spdy.StreamId(0) {
|
||||
parent, _ = s.getStream(frame.AssociatedToStreamId)
|
||||
}
|
||||
|
||||
stream := &Stream{
|
||||
streamId: frame.StreamId,
|
||||
parent: parent,
|
||||
conn: s,
|
||||
startChan: make(chan error),
|
||||
headers: frame.Headers,
|
||||
finished: (frame.CFHeader.Flags & spdy.ControlFlagUnidirectional) != 0x00,
|
||||
replyCond: sync.NewCond(new(sync.Mutex)),
|
||||
dataChan: make(chan []byte),
|
||||
headerChan: make(chan http.Header),
|
||||
closeChan: make(chan bool),
|
||||
priority: frame.Priority,
|
||||
}
|
||||
if frame.CFHeader.Flags&spdy.ControlFlagFin != 0x00 {
|
||||
stream.closeRemoteChannels()
|
||||
}
|
||||
|
||||
s.addStream(stream)
|
||||
}
|
||||
|
||||
// checkStreamFrame checks to see if a stream frame is allowed.
|
||||
// If the stream is invalid, then a reset frame with protocol error
|
||||
// will be returned.
|
||||
func (s *Connection) checkStreamFrame(frame *spdy.SynStreamFrame) bool {
|
||||
s.receiveIdLock.Lock()
|
||||
defer s.receiveIdLock.Unlock()
|
||||
if s.goneAway {
|
||||
return false
|
||||
}
|
||||
validationErr := s.validateStreamId(frame.StreamId)
|
||||
if validationErr != nil {
|
||||
go func() {
|
||||
resetErr := s.sendResetFrame(spdy.ProtocolError, frame.StreamId)
|
||||
if resetErr != nil {
|
||||
debugMessage("reset error: %s", resetErr)
|
||||
}
|
||||
}()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Connection) handleStreamFrame(frame *spdy.SynStreamFrame, newHandler StreamHandler) error {
|
||||
stream, ok := s.getStream(frame.StreamId)
|
||||
if !ok {
|
||||
return fmt.Errorf("Missing stream: %d", frame.StreamId)
|
||||
}
|
||||
|
||||
newHandler(stream)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleReplyFrame(frame *spdy.SynReplyFrame) error {
|
||||
debugMessage("(%p) Reply frame received for %d", s, frame.StreamId)
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
debugMessage("Reply frame gone away for %d", frame.StreamId)
|
||||
// Stream has already gone away
|
||||
return nil
|
||||
}
|
||||
if stream.replied {
|
||||
// Stream has already received reply
|
||||
return nil
|
||||
}
|
||||
stream.replied = true
|
||||
|
||||
// TODO Check for error
|
||||
if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 {
|
||||
s.remoteStreamFinish(stream)
|
||||
}
|
||||
|
||||
close(stream.startChan)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleResetFrame(frame *spdy.RstStreamFrame) error {
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
// Stream has already been removed
|
||||
return nil
|
||||
}
|
||||
s.removeStream(stream)
|
||||
stream.closeRemoteChannels()
|
||||
|
||||
if !stream.replied {
|
||||
stream.replied = true
|
||||
stream.startChan <- ErrReset
|
||||
close(stream.startChan)
|
||||
}
|
||||
|
||||
stream.finishLock.Lock()
|
||||
stream.finished = true
|
||||
stream.finishLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleHeaderFrame(frame *spdy.HeadersFrame) error {
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
// Stream has already gone away
|
||||
return nil
|
||||
}
|
||||
if !stream.replied {
|
||||
// No reply received...Protocol error?
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO limit headers while not blocking (use buffered chan or goroutine?)
|
||||
select {
|
||||
case <-stream.closeChan:
|
||||
return nil
|
||||
case stream.headerChan <- frame.Headers:
|
||||
}
|
||||
|
||||
if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 {
|
||||
s.remoteStreamFinish(stream)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleDataFrame(frame *spdy.DataFrame) error {
|
||||
debugMessage("(%p) Data frame received for %d", s, frame.StreamId)
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
debugMessage("(%p) Data frame gone away for %d", s, frame.StreamId)
|
||||
// Stream has already gone away
|
||||
return nil
|
||||
}
|
||||
if !stream.replied {
|
||||
debugMessage("(%p) Data frame not replied %d", s, frame.StreamId)
|
||||
// No reply received...Protocol error?
|
||||
return nil
|
||||
}
|
||||
|
||||
debugMessage("(%p) (%d) Data frame handling", stream, stream.streamId)
|
||||
if len(frame.Data) > 0 {
|
||||
stream.dataLock.RLock()
|
||||
select {
|
||||
case <-stream.closeChan:
|
||||
debugMessage("(%p) (%d) Data frame not sent (stream shut down)", stream, stream.streamId)
|
||||
case stream.dataChan <- frame.Data:
|
||||
debugMessage("(%p) (%d) Data frame sent", stream, stream.streamId)
|
||||
}
|
||||
stream.dataLock.RUnlock()
|
||||
}
|
||||
if (frame.Flags & spdy.DataFlagFin) != 0x00 {
|
||||
s.remoteStreamFinish(stream)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handlePingFrame(frame *spdy.PingFrame) error {
|
||||
if s.pingId&0x01 != frame.Id&0x01 {
|
||||
return s.framer.WriteFrame(frame)
|
||||
}
|
||||
pingChan, pingOk := s.pingChans[frame.Id]
|
||||
if pingOk {
|
||||
close(pingChan)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleGoAwayFrame(frame *spdy.GoAwayFrame) error {
|
||||
debugMessage("(%p) Go away received", s)
|
||||
s.receiveIdLock.Lock()
|
||||
if s.goneAway {
|
||||
s.receiveIdLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
s.goneAway = true
|
||||
s.receiveIdLock.Unlock()
|
||||
|
||||
if s.lastStreamChan != nil {
|
||||
stream, _ := s.getStream(frame.LastGoodStreamId)
|
||||
go func() {
|
||||
s.lastStreamChan <- stream
|
||||
}()
|
||||
}
|
||||
|
||||
// Do not block frame handler waiting for closure
|
||||
go s.shutdown(s.goAwayTimeout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) remoteStreamFinish(stream *Stream) {
|
||||
stream.closeRemoteChannels()
|
||||
|
||||
stream.finishLock.Lock()
|
||||
if stream.finished {
|
||||
// Stream is fully closed, cleanup
|
||||
s.removeStream(stream)
|
||||
}
|
||||
stream.finishLock.Unlock()
|
||||
}
|
||||
|
||||
// CreateStream creates a new spdy stream using the parameters for
|
||||
// creating the stream frame. The stream frame will be sent upon
|
||||
// calling this function, however this function does not wait for
|
||||
// the reply frame. If waiting for the reply is desired, use
|
||||
// the stream Wait or WaitTimeout function on the stream returned
|
||||
// by this function.
|
||||
func (s *Connection) CreateStream(headers http.Header, parent *Stream, fin bool) (*Stream, error) {
|
||||
// MUST synchronize stream creation (all the way to writing the frame)
|
||||
// as stream IDs **MUST** increase monotonically.
|
||||
s.nextIdLock.Lock()
|
||||
defer s.nextIdLock.Unlock()
|
||||
|
||||
streamId := s.getNextStreamId()
|
||||
if streamId == 0 {
|
||||
return nil, fmt.Errorf("Unable to get new stream id")
|
||||
}
|
||||
|
||||
stream := &Stream{
|
||||
streamId: streamId,
|
||||
parent: parent,
|
||||
conn: s,
|
||||
startChan: make(chan error),
|
||||
headers: headers,
|
||||
dataChan: make(chan []byte),
|
||||
headerChan: make(chan http.Header),
|
||||
closeChan: make(chan bool),
|
||||
}
|
||||
|
||||
debugMessage("(%p) (%p) Create stream", s, stream)
|
||||
|
||||
s.addStream(stream)
|
||||
|
||||
return stream, s.sendStream(stream, fin)
|
||||
}
|
||||
|
||||
func (s *Connection) shutdown(closeTimeout time.Duration) {
|
||||
// TODO Ensure this isn't called multiple times
|
||||
s.shutdownLock.Lock()
|
||||
if s.hasShutdown {
|
||||
s.shutdownLock.Unlock()
|
||||
return
|
||||
}
|
||||
s.hasShutdown = true
|
||||
s.shutdownLock.Unlock()
|
||||
|
||||
var timeout <-chan time.Time
|
||||
if closeTimeout > time.Duration(0) {
|
||||
timeout = time.After(closeTimeout)
|
||||
}
|
||||
streamsClosed := make(chan bool)
|
||||
|
||||
go func() {
|
||||
s.streamCond.L.Lock()
|
||||
for len(s.streams) > 0 {
|
||||
debugMessage("Streams opened: %d, %#v", len(s.streams), s.streams)
|
||||
s.streamCond.Wait()
|
||||
}
|
||||
s.streamCond.L.Unlock()
|
||||
close(streamsClosed)
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case <-streamsClosed:
|
||||
// No active streams, close should be safe
|
||||
err = s.conn.Close()
|
||||
case <-timeout:
|
||||
// Force ungraceful close
|
||||
err = s.conn.Close()
|
||||
// Wait for cleanup to clear active streams
|
||||
<-streamsClosed
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
duration := 10 * time.Minute
|
||||
time.AfterFunc(duration, func() {
|
||||
select {
|
||||
case err, ok := <-s.shutdownChan:
|
||||
if ok {
|
||||
debugMessage("Unhandled close error after %s: %s", duration, err)
|
||||
}
|
||||
default:
|
||||
}
|
||||
})
|
||||
s.shutdownChan <- err
|
||||
}
|
||||
close(s.shutdownChan)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Closes spdy connection by sending GoAway frame and initiating shutdown
|
||||
func (s *Connection) Close() error {
|
||||
s.receiveIdLock.Lock()
|
||||
if s.goneAway {
|
||||
s.receiveIdLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
s.goneAway = true
|
||||
s.receiveIdLock.Unlock()
|
||||
|
||||
var lastStreamId spdy.StreamId
|
||||
if s.receivedStreamId > 2 {
|
||||
lastStreamId = s.receivedStreamId - 2
|
||||
}
|
||||
|
||||
goAwayFrame := &spdy.GoAwayFrame{
|
||||
LastGoodStreamId: lastStreamId,
|
||||
Status: spdy.GoAwayOK,
|
||||
}
|
||||
|
||||
err := s.framer.WriteFrame(goAwayFrame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go s.shutdown(s.closeTimeout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWait closes the connection and waits for shutdown
|
||||
// to finish. Note the underlying network Connection
|
||||
// is not closed until the end of shutdown.
|
||||
func (s *Connection) CloseWait() error {
|
||||
closeErr := s.Close()
|
||||
if closeErr != nil {
|
||||
return closeErr
|
||||
}
|
||||
shutdownErr, ok := <-s.shutdownChan
|
||||
if ok {
|
||||
return shutdownErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the connection to finish shutdown or for
|
||||
// the wait timeout duration to expire. This needs to be
|
||||
// called either after Close has been called or the GOAWAYFRAME
|
||||
// has been received. If the wait timeout is 0, this function
|
||||
// will block until shutdown finishes. If wait is never called
|
||||
// and a shutdown error occurs, that error will be logged as an
|
||||
// unhandled error.
|
||||
func (s *Connection) Wait(waitTimeout time.Duration) error {
|
||||
var timeout <-chan time.Time
|
||||
if waitTimeout > time.Duration(0) {
|
||||
timeout = time.After(waitTimeout)
|
||||
}
|
||||
|
||||
select {
|
||||
case err, ok := <-s.shutdownChan:
|
||||
if ok {
|
||||
return err
|
||||
}
|
||||
case <-timeout:
|
||||
return ErrTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyClose registers a channel to be called when the remote
|
||||
// peer inidicates connection closure. The last stream to be
|
||||
// received by the remote will be sent on the channel. The notify
|
||||
// timeout will determine the duration between go away received
|
||||
// and the connection being closed.
|
||||
func (s *Connection) NotifyClose(c chan<- *Stream, timeout time.Duration) {
|
||||
s.goAwayTimeout = timeout
|
||||
s.lastStreamChan = c
|
||||
}
|
||||
|
||||
// SetCloseTimeout sets the amount of time close will wait for
|
||||
// streams to finish before terminating the underlying network
|
||||
// connection. Setting the timeout to 0 will cause close to
|
||||
// wait forever, which is the default.
|
||||
func (s *Connection) SetCloseTimeout(timeout time.Duration) {
|
||||
s.closeTimeout = timeout
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the amount of time the connection may sit idle before
|
||||
// it is forcefully terminated.
|
||||
func (s *Connection) SetIdleTimeout(timeout time.Duration) {
|
||||
s.framer.setIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (s *Connection) sendHeaders(headers http.Header, stream *Stream, fin bool) error {
|
||||
var flags spdy.ControlFlags
|
||||
if fin {
|
||||
flags = spdy.ControlFlagFin
|
||||
}
|
||||
|
||||
headerFrame := &spdy.HeadersFrame{
|
||||
StreamId: stream.streamId,
|
||||
Headers: headers,
|
||||
CFHeader: spdy.ControlFrameHeader{Flags: flags},
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(headerFrame)
|
||||
}
|
||||
|
||||
func (s *Connection) sendReply(headers http.Header, stream *Stream, fin bool) error {
|
||||
var flags spdy.ControlFlags
|
||||
if fin {
|
||||
flags = spdy.ControlFlagFin
|
||||
}
|
||||
|
||||
replyFrame := &spdy.SynReplyFrame{
|
||||
StreamId: stream.streamId,
|
||||
Headers: headers,
|
||||
CFHeader: spdy.ControlFrameHeader{Flags: flags},
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(replyFrame)
|
||||
}
|
||||
|
||||
func (s *Connection) sendResetFrame(status spdy.RstStreamStatus, streamId spdy.StreamId) error {
|
||||
resetFrame := &spdy.RstStreamFrame{
|
||||
StreamId: streamId,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(resetFrame)
|
||||
}
|
||||
|
||||
func (s *Connection) sendReset(status spdy.RstStreamStatus, stream *Stream) error {
|
||||
return s.sendResetFrame(status, stream.streamId)
|
||||
}
|
||||
|
||||
func (s *Connection) sendStream(stream *Stream, fin bool) error {
|
||||
var flags spdy.ControlFlags
|
||||
if fin {
|
||||
flags = spdy.ControlFlagFin
|
||||
stream.finished = true
|
||||
}
|
||||
|
||||
var parentId spdy.StreamId
|
||||
if stream.parent != nil {
|
||||
parentId = stream.parent.streamId
|
||||
}
|
||||
|
||||
streamFrame := &spdy.SynStreamFrame{
|
||||
StreamId: spdy.StreamId(stream.streamId),
|
||||
AssociatedToStreamId: spdy.StreamId(parentId),
|
||||
Headers: stream.headers,
|
||||
CFHeader: spdy.ControlFrameHeader{Flags: flags},
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(streamFrame)
|
||||
}
|
||||
|
||||
// getNextStreamId returns the next sequential id
|
||||
// every call should produce a unique value or an error
|
||||
func (s *Connection) getNextStreamId() spdy.StreamId {
|
||||
sid := s.nextStreamId
|
||||
if sid > 0x7fffffff {
|
||||
return 0
|
||||
}
|
||||
s.nextStreamId = s.nextStreamId + 2
|
||||
return sid
|
||||
}
|
||||
|
||||
// PeekNextStreamId returns the next sequential id and keeps the next id untouched
|
||||
func (s *Connection) PeekNextStreamId() spdy.StreamId {
|
||||
sid := s.nextStreamId
|
||||
return sid
|
||||
}
|
||||
|
||||
func (s *Connection) validateStreamId(rid spdy.StreamId) error {
|
||||
if rid > 0x7fffffff || rid < s.receivedStreamId {
|
||||
return ErrInvalidStreamId
|
||||
}
|
||||
s.receivedStreamId = rid + 2
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) addStream(stream *Stream) {
|
||||
s.streamCond.L.Lock()
|
||||
s.streams[stream.streamId] = stream
|
||||
debugMessage("(%p) (%p) Stream added, broadcasting: %d", s, stream, stream.streamId)
|
||||
s.streamCond.Broadcast()
|
||||
s.streamCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (s *Connection) removeStream(stream *Stream) {
|
||||
s.streamCond.L.Lock()
|
||||
delete(s.streams, stream.streamId)
|
||||
debugMessage("(%p) (%p) Stream removed, broadcasting: %d", s, stream, stream.streamId)
|
||||
s.streamCond.Broadcast()
|
||||
s.streamCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (s *Connection) getStream(streamId spdy.StreamId) (stream *Stream, ok bool) {
|
||||
s.streamLock.RLock()
|
||||
stream, ok = s.streams[streamId]
|
||||
s.streamLock.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// FindStream looks up the given stream id and either waits for the
|
||||
// stream to be found or returns nil if the stream id is no longer
|
||||
// valid.
|
||||
func (s *Connection) FindStream(streamId uint32) *Stream {
|
||||
var stream *Stream
|
||||
var ok bool
|
||||
s.streamCond.L.Lock()
|
||||
stream, ok = s.streams[spdy.StreamId(streamId)]
|
||||
debugMessage("(%p) Found stream %d? %t", s, spdy.StreamId(streamId), ok)
|
||||
for !ok && streamId >= uint32(s.receivedStreamId) {
|
||||
s.streamCond.Wait()
|
||||
stream, ok = s.streams[spdy.StreamId(streamId)]
|
||||
}
|
||||
s.streamCond.L.Unlock()
|
||||
return stream
|
||||
}
|
||||
|
||||
func (s *Connection) CloseChan() <-chan bool {
|
||||
return s.closeChan
|
||||
}
|
||||
38
vendor/github.com/docker/spdystream/handlers.go
generated
vendored
Normal file
38
vendor/github.com/docker/spdystream/handlers.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package spdystream
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// MirrorStreamHandler mirrors all streams.
|
||||
func MirrorStreamHandler(stream *Stream) {
|
||||
replyErr := stream.SendReply(http.Header{}, false)
|
||||
if replyErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
io.Copy(stream, stream)
|
||||
stream.Close()
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
header, receiveErr := stream.ReceiveHeader()
|
||||
if receiveErr != nil {
|
||||
return
|
||||
}
|
||||
sendErr := stream.SendHeader(header, false)
|
||||
if sendErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// NoopStreamHandler does nothing when stream connects, most
|
||||
// likely used with RejectAuthHandler which will not allow any
|
||||
// streams to make it to the stream handler.
|
||||
func NoOpStreamHandler(stream *Stream) {
|
||||
stream.SendReply(http.Header{}, false)
|
||||
}
|
||||
98
vendor/github.com/docker/spdystream/priority.go
generated
vendored
Normal file
98
vendor/github.com/docker/spdystream/priority.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package spdystream
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
type prioritizedFrame struct {
|
||||
frame spdy.Frame
|
||||
priority uint8
|
||||
insertId uint64
|
||||
}
|
||||
|
||||
type frameQueue []*prioritizedFrame
|
||||
|
||||
func (fq frameQueue) Len() int {
|
||||
return len(fq)
|
||||
}
|
||||
|
||||
func (fq frameQueue) Less(i, j int) bool {
|
||||
if fq[i].priority == fq[j].priority {
|
||||
return fq[i].insertId < fq[j].insertId
|
||||
}
|
||||
return fq[i].priority < fq[j].priority
|
||||
}
|
||||
|
||||
func (fq frameQueue) Swap(i, j int) {
|
||||
fq[i], fq[j] = fq[j], fq[i]
|
||||
}
|
||||
|
||||
func (fq *frameQueue) Push(x interface{}) {
|
||||
*fq = append(*fq, x.(*prioritizedFrame))
|
||||
}
|
||||
|
||||
func (fq *frameQueue) Pop() interface{} {
|
||||
old := *fq
|
||||
n := len(old)
|
||||
*fq = old[0 : n-1]
|
||||
return old[n-1]
|
||||
}
|
||||
|
||||
type PriorityFrameQueue struct {
|
||||
queue *frameQueue
|
||||
c *sync.Cond
|
||||
size int
|
||||
nextInsertId uint64
|
||||
drain bool
|
||||
}
|
||||
|
||||
func NewPriorityFrameQueue(size int) *PriorityFrameQueue {
|
||||
queue := make(frameQueue, 0, size)
|
||||
heap.Init(&queue)
|
||||
|
||||
return &PriorityFrameQueue{
|
||||
queue: &queue,
|
||||
size: size,
|
||||
c: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *PriorityFrameQueue) Push(frame spdy.Frame, priority uint8) {
|
||||
q.c.L.Lock()
|
||||
defer q.c.L.Unlock()
|
||||
for q.queue.Len() >= q.size {
|
||||
q.c.Wait()
|
||||
}
|
||||
pFrame := &prioritizedFrame{
|
||||
frame: frame,
|
||||
priority: priority,
|
||||
insertId: q.nextInsertId,
|
||||
}
|
||||
q.nextInsertId = q.nextInsertId + 1
|
||||
heap.Push(q.queue, pFrame)
|
||||
q.c.Signal()
|
||||
}
|
||||
|
||||
func (q *PriorityFrameQueue) Pop() spdy.Frame {
|
||||
q.c.L.Lock()
|
||||
defer q.c.L.Unlock()
|
||||
for q.queue.Len() == 0 {
|
||||
if q.drain {
|
||||
return nil
|
||||
}
|
||||
q.c.Wait()
|
||||
}
|
||||
frame := heap.Pop(q.queue).(*prioritizedFrame).frame
|
||||
q.c.Signal()
|
||||
return frame
|
||||
}
|
||||
|
||||
func (q *PriorityFrameQueue) Drain() {
|
||||
q.c.L.Lock()
|
||||
defer q.c.L.Unlock()
|
||||
q.drain = true
|
||||
q.c.Broadcast()
|
||||
}
|
||||
187
vendor/github.com/docker/spdystream/spdy/dictionary.go
generated
vendored
Normal file
187
vendor/github.com/docker/spdystream/spdy/dictionary.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
// headerDictionary is the dictionary sent to the zlib compressor/decompressor.
|
||||
var headerDictionary = []byte{
|
||||
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68,
|
||||
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70,
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70,
|
||||
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05,
|
||||
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00,
|
||||
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
|
||||
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63,
|
||||
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f,
|
||||
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,
|
||||
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00,
|
||||
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73,
|
||||
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00,
|
||||
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63,
|
||||
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65,
|
||||
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,
|
||||
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
|
||||
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67,
|
||||
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00,
|
||||
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
|
||||
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00,
|
||||
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00,
|
||||
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00,
|
||||
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
|
||||
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69,
|
||||
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66,
|
||||
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69,
|
||||
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00,
|
||||
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f,
|
||||
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73,
|
||||
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d,
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00,
|
||||
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67,
|
||||
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d,
|
||||
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,
|
||||
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74,
|
||||
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,
|
||||
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72,
|
||||
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00,
|
||||
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00,
|
||||
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,
|
||||
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05,
|
||||
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00,
|
||||
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72,
|
||||
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72,
|
||||
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00,
|
||||
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00,
|
||||
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c,
|
||||
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72,
|
||||
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65,
|
||||
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00,
|
||||
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,
|
||||
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73,
|
||||
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79,
|
||||
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00,
|
||||
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69,
|
||||
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77,
|
||||
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00,
|
||||
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
|
||||
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00,
|
||||
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30,
|
||||
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,
|
||||
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72,
|
||||
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73,
|
||||
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69,
|
||||
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65,
|
||||
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00,
|
||||
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69,
|
||||
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32,
|
||||
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35,
|
||||
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30,
|
||||
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33,
|
||||
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37,
|
||||
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30,
|
||||
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34,
|
||||
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31,
|
||||
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31,
|
||||
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34,
|
||||
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34,
|
||||
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e,
|
||||
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65,
|
||||
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20,
|
||||
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65,
|
||||
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f,
|
||||
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d,
|
||||
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34,
|
||||
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30,
|
||||
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30,
|
||||
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,
|
||||
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e,
|
||||
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64,
|
||||
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f,
|
||||
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74,
|
||||
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20,
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20,
|
||||
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46,
|
||||
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41,
|
||||
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a,
|
||||
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41,
|
||||
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20,
|
||||
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20,
|
||||
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30,
|
||||
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e,
|
||||
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57,
|
||||
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c,
|
||||
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61,
|
||||
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20,
|
||||
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b,
|
||||
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61,
|
||||
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69,
|
||||
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67,
|
||||
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67,
|
||||
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
|
||||
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c,
|
||||
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c,
|
||||
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74,
|
||||
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
|
||||
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65,
|
||||
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65,
|
||||
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64,
|
||||
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
|
||||
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63,
|
||||
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69,
|
||||
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d,
|
||||
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a,
|
||||
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e,
|
||||
}
|
||||
348
vendor/github.com/docker/spdystream/spdy/read.go
generated
vendored
Normal file
348
vendor/github.com/docker/spdystream/spdy/read.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
return f.readSynStreamFrame(h, frame)
|
||||
}
|
||||
|
||||
func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
return f.readSynReplyFrame(h, frame)
|
||||
}
|
||||
|
||||
func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.Status == 0 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
var numSettings uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings)
|
||||
for i := uint32(0); i < numSettings; i++ {
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil {
|
||||
return err
|
||||
}
|
||||
frame.FlagIdValues[i].Flag = SettingsFlag((frame.FlagIdValues[i].Id & 0xff000000) >> 24)
|
||||
frame.FlagIdValues[i].Id &= 0xffffff
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.Id == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
if frame.CFHeader.Flags != 0 {
|
||||
return &Error{InvalidControlFrame, StreamId(frame.Id)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.CFHeader.Flags != 0 {
|
||||
return &Error{InvalidControlFrame, frame.LastGoodStreamId}
|
||||
}
|
||||
if frame.CFHeader.length != 8 {
|
||||
return &Error{InvalidControlFrame, frame.LastGoodStreamId}
|
||||
}
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
return f.readHeadersFrame(h, frame)
|
||||
}
|
||||
|
||||
func (frame *WindowUpdateFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.CFHeader.Flags != 0 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if frame.CFHeader.length != 8 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.DeltaWindowSize); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newControlFrame(frameType ControlFrameType) (controlFrame, error) {
|
||||
ctor, ok := cframeCtor[frameType]
|
||||
if !ok {
|
||||
return nil, &Error{Err: InvalidControlFrame}
|
||||
}
|
||||
return ctor(), nil
|
||||
}
|
||||
|
||||
var cframeCtor = map[ControlFrameType]func() controlFrame{
|
||||
TypeSynStream: func() controlFrame { return new(SynStreamFrame) },
|
||||
TypeSynReply: func() controlFrame { return new(SynReplyFrame) },
|
||||
TypeRstStream: func() controlFrame { return new(RstStreamFrame) },
|
||||
TypeSettings: func() controlFrame { return new(SettingsFrame) },
|
||||
TypePing: func() controlFrame { return new(PingFrame) },
|
||||
TypeGoAway: func() controlFrame { return new(GoAwayFrame) },
|
||||
TypeHeaders: func() controlFrame { return new(HeadersFrame) },
|
||||
TypeWindowUpdate: func() controlFrame { return new(WindowUpdateFrame) },
|
||||
}
|
||||
|
||||
func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) error {
|
||||
if f.headerDecompressor != nil {
|
||||
f.headerReader.N = payloadSize
|
||||
return nil
|
||||
}
|
||||
f.headerReader = io.LimitedReader{R: f.r, N: payloadSize}
|
||||
decompressor, err := zlib.NewReaderDict(&f.headerReader, []byte(headerDictionary))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.headerDecompressor = decompressor
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFrame reads SPDY encoded data and returns a decompressed Frame.
|
||||
func (f *Framer) ReadFrame() (Frame, error) {
|
||||
var firstWord uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &firstWord); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if firstWord&0x80000000 != 0 {
|
||||
frameType := ControlFrameType(firstWord & 0xffff)
|
||||
version := uint16(firstWord >> 16 & 0x7fff)
|
||||
return f.parseControlFrame(version, frameType)
|
||||
}
|
||||
return f.parseDataFrame(StreamId(firstWord & 0x7fffffff))
|
||||
}
|
||||
|
||||
func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (Frame, error) {
|
||||
var length uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags := ControlFlags((length & 0xff000000) >> 24)
|
||||
length &= 0xffffff
|
||||
header := ControlFrameHeader{version, frameType, flags, length}
|
||||
cframe, err := newControlFrame(frameType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = cframe.read(header, f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cframe, nil
|
||||
}
|
||||
|
||||
func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) {
|
||||
var numHeaders uint32
|
||||
if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var e error
|
||||
h := make(http.Header, int(numHeaders))
|
||||
for i := 0; i < int(numHeaders); i++ {
|
||||
var length uint32
|
||||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameBytes := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, nameBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := string(nameBytes)
|
||||
if name != strings.ToLower(name) {
|
||||
e = &Error{UnlowercasedHeaderName, streamId}
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
if h[name] != nil {
|
||||
e = &Error{DuplicateHeaders, streamId}
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueList := strings.Split(string(value), headerValueSeparator)
|
||||
for _, v := range valueList {
|
||||
h.Add(name, v)
|
||||
}
|
||||
}
|
||||
if e != nil {
|
||||
return h, e
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) error {
|
||||
frame.CFHeader = h
|
||||
var err error
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.AssociatedToStreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.Priority); err != nil {
|
||||
return err
|
||||
}
|
||||
frame.Priority >>= 5
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.Slot); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := f.r
|
||||
if !f.headerCompressionDisabled {
|
||||
err := f.uncorkHeaderDecompressor(int64(h.length - 10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader = f.headerDecompressor
|
||||
}
|
||||
frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
|
||||
if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) {
|
||||
err = &Error{WrongCompressedPayloadSize, 0}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for h := range frame.Headers {
|
||||
if invalidReqHeaders[h] {
|
||||
return &Error{InvalidHeaderPresent, frame.StreamId}
|
||||
}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) error {
|
||||
frame.CFHeader = h
|
||||
var err error
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := f.r
|
||||
if !f.headerCompressionDisabled {
|
||||
err := f.uncorkHeaderDecompressor(int64(h.length - 4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader = f.headerDecompressor
|
||||
}
|
||||
frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
|
||||
if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) {
|
||||
err = &Error{WrongCompressedPayloadSize, 0}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for h := range frame.Headers {
|
||||
if invalidRespHeaders[h] {
|
||||
return &Error{InvalidHeaderPresent, frame.StreamId}
|
||||
}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) error {
|
||||
frame.CFHeader = h
|
||||
var err error
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := f.r
|
||||
if !f.headerCompressionDisabled {
|
||||
err := f.uncorkHeaderDecompressor(int64(h.length - 4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader = f.headerDecompressor
|
||||
}
|
||||
frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
|
||||
if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) {
|
||||
err = &Error{WrongCompressedPayloadSize, 0}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var invalidHeaders map[string]bool
|
||||
if frame.StreamId%2 == 0 {
|
||||
invalidHeaders = invalidReqHeaders
|
||||
} else {
|
||||
invalidHeaders = invalidRespHeaders
|
||||
}
|
||||
for h := range frame.Headers {
|
||||
if invalidHeaders[h] {
|
||||
return &Error{InvalidHeaderPresent, frame.StreamId}
|
||||
}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) parseDataFrame(streamId StreamId) (*DataFrame, error) {
|
||||
var length uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var frame DataFrame
|
||||
frame.StreamId = streamId
|
||||
frame.Flags = DataFlags(length >> 24)
|
||||
length &= 0xffffff
|
||||
frame.Data = make([]byte, length)
|
||||
if _, err := io.ReadFull(f.r, frame.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return nil, &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return &frame, nil
|
||||
}
|
||||
275
vendor/github.com/docker/spdystream/spdy/types.go
generated
vendored
Normal file
275
vendor/github.com/docker/spdystream/spdy/types.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package spdy implements the SPDY protocol (currently SPDY/3), described in
|
||||
// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3.
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Version is the protocol version number that this package implements.
|
||||
const Version = 3
|
||||
|
||||
// ControlFrameType stores the type field in a control frame header.
|
||||
type ControlFrameType uint16
|
||||
|
||||
const (
|
||||
TypeSynStream ControlFrameType = 0x0001
|
||||
TypeSynReply = 0x0002
|
||||
TypeRstStream = 0x0003
|
||||
TypeSettings = 0x0004
|
||||
TypePing = 0x0006
|
||||
TypeGoAway = 0x0007
|
||||
TypeHeaders = 0x0008
|
||||
TypeWindowUpdate = 0x0009
|
||||
)
|
||||
|
||||
// ControlFlags are the flags that can be set on a control frame.
|
||||
type ControlFlags uint8
|
||||
|
||||
const (
|
||||
ControlFlagFin ControlFlags = 0x01
|
||||
ControlFlagUnidirectional = 0x02
|
||||
ControlFlagSettingsClearSettings = 0x01
|
||||
)
|
||||
|
||||
// DataFlags are the flags that can be set on a data frame.
|
||||
type DataFlags uint8
|
||||
|
||||
const (
|
||||
DataFlagFin DataFlags = 0x01
|
||||
)
|
||||
|
||||
// MaxDataLength is the maximum number of bytes that can be stored in one frame.
|
||||
const MaxDataLength = 1<<24 - 1
|
||||
|
||||
// headerValueSepator separates multiple header values.
|
||||
const headerValueSeparator = "\x00"
|
||||
|
||||
// Frame is a single SPDY frame in its unpacked in-memory representation. Use
|
||||
// Framer to read and write it.
|
||||
type Frame interface {
|
||||
write(f *Framer) error
|
||||
}
|
||||
|
||||
// ControlFrameHeader contains all the fields in a control frame header,
|
||||
// in its unpacked in-memory representation.
|
||||
type ControlFrameHeader struct {
|
||||
// Note, high bit is the "Control" bit.
|
||||
version uint16 // spdy version number
|
||||
frameType ControlFrameType
|
||||
Flags ControlFlags
|
||||
length uint32 // length of data field
|
||||
}
|
||||
|
||||
type controlFrame interface {
|
||||
Frame
|
||||
read(h ControlFrameHeader, f *Framer) error
|
||||
}
|
||||
|
||||
// StreamId represents a 31-bit value identifying the stream.
|
||||
type StreamId uint32
|
||||
|
||||
// SynStreamFrame is the unpacked, in-memory representation of a SYN_STREAM
|
||||
// frame.
|
||||
type SynStreamFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
AssociatedToStreamId StreamId // stream id for a stream which this stream is associated to
|
||||
Priority uint8 // priority of this frame (3-bit)
|
||||
Slot uint8 // index in the server's credential vector of the client certificate
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame.
|
||||
type SynReplyFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
// RstStreamStatus represents the status that led to a RST_STREAM.
|
||||
type RstStreamStatus uint32
|
||||
|
||||
const (
|
||||
ProtocolError RstStreamStatus = iota + 1
|
||||
InvalidStream
|
||||
RefusedStream
|
||||
UnsupportedVersion
|
||||
Cancel
|
||||
InternalError
|
||||
FlowControlError
|
||||
StreamInUse
|
||||
StreamAlreadyClosed
|
||||
InvalidCredentials
|
||||
FrameTooLarge
|
||||
)
|
||||
|
||||
// RstStreamFrame is the unpacked, in-memory representation of a RST_STREAM
|
||||
// frame.
|
||||
type RstStreamFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
Status RstStreamStatus
|
||||
}
|
||||
|
||||
// SettingsFlag represents a flag in a SETTINGS frame.
|
||||
type SettingsFlag uint8
|
||||
|
||||
const (
|
||||
FlagSettingsPersistValue SettingsFlag = 0x1
|
||||
FlagSettingsPersisted = 0x2
|
||||
)
|
||||
|
||||
// SettingsFlag represents the id of an id/value pair in a SETTINGS frame.
|
||||
type SettingsId uint32
|
||||
|
||||
const (
|
||||
SettingsUploadBandwidth SettingsId = iota + 1
|
||||
SettingsDownloadBandwidth
|
||||
SettingsRoundTripTime
|
||||
SettingsMaxConcurrentStreams
|
||||
SettingsCurrentCwnd
|
||||
SettingsDownloadRetransRate
|
||||
SettingsInitialWindowSize
|
||||
SettingsClientCretificateVectorSize
|
||||
)
|
||||
|
||||
// SettingsFlagIdValue is the unpacked, in-memory representation of the
|
||||
// combined flag/id/value for a setting in a SETTINGS frame.
|
||||
type SettingsFlagIdValue struct {
|
||||
Flag SettingsFlag
|
||||
Id SettingsId
|
||||
Value uint32
|
||||
}
|
||||
|
||||
// SettingsFrame is the unpacked, in-memory representation of a SPDY
|
||||
// SETTINGS frame.
|
||||
type SettingsFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
FlagIdValues []SettingsFlagIdValue
|
||||
}
|
||||
|
||||
// PingFrame is the unpacked, in-memory representation of a PING frame.
|
||||
type PingFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
Id uint32 // unique id for this ping, from server is even, from client is odd.
|
||||
}
|
||||
|
||||
// GoAwayStatus represents the status in a GoAwayFrame.
|
||||
type GoAwayStatus uint32
|
||||
|
||||
const (
|
||||
GoAwayOK GoAwayStatus = iota
|
||||
GoAwayProtocolError
|
||||
GoAwayInternalError
|
||||
)
|
||||
|
||||
// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame.
|
||||
type GoAwayFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
LastGoodStreamId StreamId // last stream id which was accepted by sender
|
||||
Status GoAwayStatus
|
||||
}
|
||||
|
||||
// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame.
|
||||
type HeadersFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
// WindowUpdateFrame is the unpacked, in-memory representation of a
|
||||
// WINDOW_UPDATE frame.
|
||||
type WindowUpdateFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
DeltaWindowSize uint32 // additional number of bytes to existing window size
|
||||
}
|
||||
|
||||
// TODO: Implement credential frame and related methods.
|
||||
|
||||
// DataFrame is the unpacked, in-memory representation of a DATA frame.
|
||||
type DataFrame struct {
|
||||
// Note, high bit is the "Control" bit. Should be 0 for data frames.
|
||||
StreamId StreamId
|
||||
Flags DataFlags
|
||||
Data []byte // payload data of this frame
|
||||
}
|
||||
|
||||
// A SPDY specific error.
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
UnlowercasedHeaderName ErrorCode = "header was not lowercased"
|
||||
DuplicateHeaders = "multiple headers with same name"
|
||||
WrongCompressedPayloadSize = "compressed payload size was incorrect"
|
||||
UnknownFrameType = "unknown frame type"
|
||||
InvalidControlFrame = "invalid control frame"
|
||||
InvalidDataFrame = "invalid data frame"
|
||||
InvalidHeaderPresent = "frame contained invalid header"
|
||||
ZeroStreamId = "stream id zero is disallowed"
|
||||
)
|
||||
|
||||
// Error contains both the type of error and additional values. StreamId is 0
|
||||
// if Error is not associated with a stream.
|
||||
type Error struct {
|
||||
Err ErrorCode
|
||||
StreamId StreamId
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return string(e.Err)
|
||||
}
|
||||
|
||||
var invalidReqHeaders = map[string]bool{
|
||||
"Connection": true,
|
||||
"Host": true,
|
||||
"Keep-Alive": true,
|
||||
"Proxy-Connection": true,
|
||||
"Transfer-Encoding": true,
|
||||
}
|
||||
|
||||
var invalidRespHeaders = map[string]bool{
|
||||
"Connection": true,
|
||||
"Keep-Alive": true,
|
||||
"Proxy-Connection": true,
|
||||
"Transfer-Encoding": true,
|
||||
}
|
||||
|
||||
// Framer handles serializing/deserializing SPDY frames, including compressing/
|
||||
// decompressing payloads.
|
||||
type Framer struct {
|
||||
headerCompressionDisabled bool
|
||||
w io.Writer
|
||||
headerBuf *bytes.Buffer
|
||||
headerCompressor *zlib.Writer
|
||||
r io.Reader
|
||||
headerReader io.LimitedReader
|
||||
headerDecompressor io.ReadCloser
|
||||
}
|
||||
|
||||
// NewFramer allocates a new Framer for a given SPDY connection, represented by
|
||||
// a io.Writer and io.Reader. Note that Framer will read and write individual fields
|
||||
// from/to the Reader and Writer, so the caller should pass in an appropriately
|
||||
// buffered implementation to optimize performance.
|
||||
func NewFramer(w io.Writer, r io.Reader) (*Framer, error) {
|
||||
compressBuf := new(bytes.Buffer)
|
||||
compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(headerDictionary))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
framer := &Framer{
|
||||
w: w,
|
||||
headerBuf: compressBuf,
|
||||
headerCompressor: compressor,
|
||||
r: r,
|
||||
}
|
||||
return framer, nil
|
||||
}
|
||||
318
vendor/github.com/docker/spdystream/spdy/write.go
generated
vendored
Normal file
318
vendor/github.com/docker/spdystream/spdy/write.go
generated
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (frame *SynStreamFrame) write(f *Framer) error {
|
||||
return f.writeSynStreamFrame(frame)
|
||||
}
|
||||
|
||||
func (frame *SynReplyFrame) write(f *Framer) error {
|
||||
return f.writeSynReplyFrame(frame)
|
||||
}
|
||||
|
||||
func (frame *RstStreamFrame) write(f *Framer) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeRstStream
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 8
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if frame.Status == 0 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (frame *SettingsFrame) write(f *Framer) (err error) {
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeSettings
|
||||
frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil {
|
||||
return
|
||||
}
|
||||
for _, flagIdValue := range frame.FlagIdValues {
|
||||
flagId := uint32(flagIdValue.Flag)<<24 | uint32(flagIdValue.Id)
|
||||
if err = binary.Write(f.w, binary.BigEndian, flagId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (frame *PingFrame) write(f *Framer) (err error) {
|
||||
if frame.Id == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypePing
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 4
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (frame *GoAwayFrame) write(f *Framer) (err error) {
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeGoAway
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 8
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *HeadersFrame) write(f *Framer) error {
|
||||
return f.writeHeadersFrame(frame)
|
||||
}
|
||||
|
||||
func (frame *WindowUpdateFrame) write(f *Framer) (err error) {
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeWindowUpdate
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 8
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.DeltaWindowSize); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *DataFrame) write(f *Framer) error {
|
||||
return f.writeDataFrame(frame)
|
||||
}
|
||||
|
||||
// WriteFrame writes a frame.
|
||||
func (f *Framer) WriteFrame(frame Frame) error {
|
||||
return frame.write(f)
|
||||
}
|
||||
|
||||
func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) error {
|
||||
if err := binary.Write(w, binary.BigEndian, 0x8000|h.version); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(w, binary.BigEndian, h.frameType); err != nil {
|
||||
return err
|
||||
}
|
||||
flagsAndLength := uint32(h.Flags)<<24 | h.length
|
||||
if err := binary.Write(w, binary.BigEndian, flagsAndLength); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err error) {
|
||||
n = 0
|
||||
if err = binary.Write(w, binary.BigEndian, uint32(len(h))); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
for name, values := range h {
|
||||
if err = binary.Write(w, binary.BigEndian, uint32(len(name))); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
name = strings.ToLower(name)
|
||||
if _, err = io.WriteString(w, name); err != nil {
|
||||
return
|
||||
}
|
||||
n += len(name)
|
||||
v := strings.Join(values, headerValueSeparator)
|
||||
if err = binary.Write(w, binary.BigEndian, uint32(len(v))); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
if _, err = io.WriteString(w, v); err != nil {
|
||||
return
|
||||
}
|
||||
n += len(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
// Marshal the headers.
|
||||
var writer io.Writer = f.headerBuf
|
||||
if !f.headerCompressionDisabled {
|
||||
writer = f.headerCompressor
|
||||
}
|
||||
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
if !f.headerCompressionDisabled {
|
||||
f.headerCompressor.Flush()
|
||||
}
|
||||
|
||||
// Set ControlFrameHeader.
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeSynStream
|
||||
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.AssociatedToStreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Priority<<5); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Slot); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
f.headerBuf.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
// Marshal the headers.
|
||||
var writer io.Writer = f.headerBuf
|
||||
if !f.headerCompressionDisabled {
|
||||
writer = f.headerCompressor
|
||||
}
|
||||
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
if !f.headerCompressionDisabled {
|
||||
f.headerCompressor.Flush()
|
||||
}
|
||||
|
||||
// Set ControlFrameHeader.
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeSynReply
|
||||
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
f.headerBuf.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
// Marshal the headers.
|
||||
var writer io.Writer = f.headerBuf
|
||||
if !f.headerCompressionDisabled {
|
||||
writer = f.headerCompressor
|
||||
}
|
||||
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
if !f.headerCompressionDisabled {
|
||||
f.headerCompressor.Flush()
|
||||
}
|
||||
|
||||
// Set ControlFrameHeader.
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeHeaders
|
||||
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
f.headerBuf.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Framer) writeDataFrame(frame *DataFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
if frame.StreamId&0x80000000 != 0 || len(frame.Data) > MaxDataLength {
|
||||
return &Error{InvalidDataFrame, frame.StreamId}
|
||||
}
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
flagsAndLength := uint32(frame.Flags)<<24 | uint32(len(frame.Data))
|
||||
if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.w.Write(frame.Data); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
327
vendor/github.com/docker/spdystream/stream.go
generated
vendored
Normal file
327
vendor/github.com/docker/spdystream/stream.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
package spdystream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnreadPartialData = errors.New("unread partial data")
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
streamId spdy.StreamId
|
||||
parent *Stream
|
||||
conn *Connection
|
||||
startChan chan error
|
||||
|
||||
dataLock sync.RWMutex
|
||||
dataChan chan []byte
|
||||
unread []byte
|
||||
|
||||
priority uint8
|
||||
headers http.Header
|
||||
headerChan chan http.Header
|
||||
finishLock sync.Mutex
|
||||
finished bool
|
||||
replyCond *sync.Cond
|
||||
replied bool
|
||||
closeLock sync.Mutex
|
||||
closeChan chan bool
|
||||
}
|
||||
|
||||
// WriteData writes data to stream, sending a dataframe per call
|
||||
func (s *Stream) WriteData(data []byte, fin bool) error {
|
||||
s.waitWriteReply()
|
||||
var flags spdy.DataFlags
|
||||
|
||||
if fin {
|
||||
flags = spdy.DataFlagFin
|
||||
s.finishLock.Lock()
|
||||
if s.finished {
|
||||
s.finishLock.Unlock()
|
||||
return ErrWriteClosedStream
|
||||
}
|
||||
s.finished = true
|
||||
s.finishLock.Unlock()
|
||||
}
|
||||
|
||||
dataFrame := &spdy.DataFrame{
|
||||
StreamId: s.streamId,
|
||||
Flags: flags,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
debugMessage("(%p) (%d) Writing data frame", s, s.streamId)
|
||||
return s.conn.framer.WriteFrame(dataFrame)
|
||||
}
|
||||
|
||||
// Write writes bytes to a stream, calling write data for each call.
|
||||
func (s *Stream) Write(data []byte) (n int, err error) {
|
||||
err = s.WriteData(data, false)
|
||||
if err == nil {
|
||||
n = len(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read reads bytes from a stream, a single read will never get more
|
||||
// than what is sent on a single data frame, but a multiple calls to
|
||||
// read may get data from the same data frame.
|
||||
func (s *Stream) Read(p []byte) (n int, err error) {
|
||||
if s.unread == nil {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return 0, io.EOF
|
||||
case read, ok := <-s.dataChan:
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
s.unread = read
|
||||
}
|
||||
}
|
||||
n = copy(p, s.unread)
|
||||
if n < len(s.unread) {
|
||||
s.unread = s.unread[n:]
|
||||
} else {
|
||||
s.unread = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadData reads an entire data frame and returns the byte array
|
||||
// from the data frame. If there is unread data from the result
|
||||
// of a Read call, this function will return an ErrUnreadPartialData.
|
||||
func (s *Stream) ReadData() ([]byte, error) {
|
||||
debugMessage("(%p) Reading data from %d", s, s.streamId)
|
||||
if s.unread != nil {
|
||||
return nil, ErrUnreadPartialData
|
||||
}
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return nil, io.EOF
|
||||
case read, ok := <-s.dataChan:
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return read, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) waitWriteReply() {
|
||||
if s.replyCond != nil {
|
||||
s.replyCond.L.Lock()
|
||||
for !s.replied {
|
||||
s.replyCond.Wait()
|
||||
}
|
||||
s.replyCond.L.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait waits for the stream to receive a reply.
|
||||
func (s *Stream) Wait() error {
|
||||
return s.WaitTimeout(time.Duration(0))
|
||||
}
|
||||
|
||||
// WaitTimeout waits for the stream to receive a reply or for timeout.
|
||||
// When the timeout is reached, ErrTimeout will be returned.
|
||||
func (s *Stream) WaitTimeout(timeout time.Duration) error {
|
||||
var timeoutChan <-chan time.Time
|
||||
if timeout > time.Duration(0) {
|
||||
timeoutChan = time.After(timeout)
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-s.startChan:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
case <-timeoutChan:
|
||||
return ErrTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the stream by sending an empty data frame with the
|
||||
// finish flag set, indicating this side is finished with the stream.
|
||||
func (s *Stream) Close() error {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
// Stream is now fully closed
|
||||
s.conn.removeStream(s)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return s.WriteData([]byte{}, true)
|
||||
}
|
||||
|
||||
// Reset sends a reset frame, putting the stream into the fully closed state.
|
||||
func (s *Stream) Reset() error {
|
||||
s.conn.removeStream(s)
|
||||
return s.resetStream()
|
||||
}
|
||||
|
||||
func (s *Stream) resetStream() error {
|
||||
// Always call closeRemoteChannels, even if s.finished is already true.
|
||||
// This makes it so that stream.Close() followed by stream.Reset() allows
|
||||
// stream.Read() to unblock.
|
||||
s.closeRemoteChannels()
|
||||
|
||||
s.finishLock.Lock()
|
||||
if s.finished {
|
||||
s.finishLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
s.finished = true
|
||||
s.finishLock.Unlock()
|
||||
|
||||
resetFrame := &spdy.RstStreamFrame{
|
||||
StreamId: s.streamId,
|
||||
Status: spdy.Cancel,
|
||||
}
|
||||
return s.conn.framer.WriteFrame(resetFrame)
|
||||
}
|
||||
|
||||
// CreateSubStream creates a stream using the current as the parent
|
||||
func (s *Stream) CreateSubStream(headers http.Header, fin bool) (*Stream, error) {
|
||||
return s.conn.CreateStream(headers, s, fin)
|
||||
}
|
||||
|
||||
// SetPriority sets the stream priority, does not affect the
|
||||
// remote priority of this stream after Open has been called.
|
||||
// Valid values are 0 through 7, 0 being the highest priority
|
||||
// and 7 the lowest.
|
||||
func (s *Stream) SetPriority(priority uint8) {
|
||||
s.priority = priority
|
||||
}
|
||||
|
||||
// SendHeader sends a header frame across the stream
|
||||
func (s *Stream) SendHeader(headers http.Header, fin bool) error {
|
||||
return s.conn.sendHeaders(headers, s, fin)
|
||||
}
|
||||
|
||||
// SendReply sends a reply on a stream, only valid to be called once
|
||||
// when handling a new stream
|
||||
func (s *Stream) SendReply(headers http.Header, fin bool) error {
|
||||
if s.replyCond == nil {
|
||||
return errors.New("cannot reply on initiated stream")
|
||||
}
|
||||
s.replyCond.L.Lock()
|
||||
defer s.replyCond.L.Unlock()
|
||||
if s.replied {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.conn.sendReply(headers, s, fin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.replied = true
|
||||
s.replyCond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refuse sends a reset frame with the status refuse, only
|
||||
// valid to be called once when handling a new stream. This
|
||||
// may be used to indicate that a stream is not allowed
|
||||
// when http status codes are not being used.
|
||||
func (s *Stream) Refuse() error {
|
||||
if s.replied {
|
||||
return nil
|
||||
}
|
||||
s.replied = true
|
||||
return s.conn.sendReset(spdy.RefusedStream, s)
|
||||
}
|
||||
|
||||
// Cancel sends a reset frame with the status canceled. This
|
||||
// can be used at any time by the creator of the Stream to
|
||||
// indicate the stream is no longer needed.
|
||||
func (s *Stream) Cancel() error {
|
||||
return s.conn.sendReset(spdy.Cancel, s)
|
||||
}
|
||||
|
||||
// ReceiveHeader receives a header sent on the other side
|
||||
// of the stream. This function will block until a header
|
||||
// is received or stream is closed.
|
||||
func (s *Stream) ReceiveHeader() (http.Header, error) {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
break
|
||||
case header, ok := <-s.headerChan:
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("header chan closed")
|
||||
}
|
||||
return header, nil
|
||||
}
|
||||
return nil, fmt.Errorf("stream closed")
|
||||
}
|
||||
|
||||
// Parent returns the parent stream
|
||||
func (s *Stream) Parent() *Stream {
|
||||
return s.parent
|
||||
}
|
||||
|
||||
// Headers returns the headers used to create the stream
|
||||
func (s *Stream) Headers() http.Header {
|
||||
return s.headers
|
||||
}
|
||||
|
||||
// String returns the string version of stream using the
|
||||
// streamId to uniquely identify the stream
|
||||
func (s *Stream) String() string {
|
||||
return fmt.Sprintf("stream:%d", s.streamId)
|
||||
}
|
||||
|
||||
// Identifier returns a 32 bit identifier for the stream
|
||||
func (s *Stream) Identifier() uint32 {
|
||||
return uint32(s.streamId)
|
||||
}
|
||||
|
||||
// IsFinished returns whether the stream has finished
|
||||
// sending data
|
||||
func (s *Stream) IsFinished() bool {
|
||||
return s.finished
|
||||
}
|
||||
|
||||
// Implement net.Conn interface
|
||||
|
||||
func (s *Stream) LocalAddr() net.Addr {
|
||||
return s.conn.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *Stream) RemoteAddr() net.Addr {
|
||||
return s.conn.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// TODO set per stream values instead of connection-wide
|
||||
|
||||
func (s *Stream) SetDeadline(t time.Time) error {
|
||||
return s.conn.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||
return s.conn.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
||||
return s.conn.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) closeRemoteChannels() {
|
||||
s.closeLock.Lock()
|
||||
defer s.closeLock.Unlock()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
default:
|
||||
close(s.closeChan)
|
||||
}
|
||||
}
|
||||
16
vendor/github.com/docker/spdystream/utils.go
generated
vendored
Normal file
16
vendor/github.com/docker/spdystream/utils.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package spdystream
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
DEBUG = os.Getenv("DEBUG")
|
||||
)
|
||||
|
||||
func debugMessage(fmt string, args ...interface{}) {
|
||||
if DEBUG != "" {
|
||||
log.Printf(fmt, args...)
|
||||
}
|
||||
}
|
||||
8
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
8
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# This is the official list of Gorilla WebSocket authors for copyright
|
||||
# purposes.
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Gary Burd <gary@beagledreams.com>
|
||||
Joachim Bauch <mail@joachim-bauch.de>
|
||||
|
||||
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
392
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
392
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrBadHandshake is returned when the server response to opening handshake is
|
||||
// invalid.
|
||||
var ErrBadHandshake = errors.New("websocket: bad handshake")
|
||||
|
||||
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
|
||||
|
||||
// NewClient creates a new client connection using the given net connection.
|
||||
// The URL u specifies the host and request URI. Use requestHeader to specify
|
||||
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
|
||||
// (Cookie). Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etc.
|
||||
//
|
||||
// Deprecated: Use Dialer instead.
|
||||
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||
d := Dialer{
|
||||
ReadBufferSize: readBufSize,
|
||||
WriteBufferSize: writeBufSize,
|
||||
NetDial: func(net, addr string) (net.Conn, error) {
|
||||
return netConn, nil
|
||||
},
|
||||
}
|
||||
return d.Dial(u.String(), requestHeader)
|
||||
}
|
||||
|
||||
// A Dialer contains options for connecting to WebSocket server.
|
||||
type Dialer struct {
|
||||
// NetDial specifies the dial function for creating TCP connections. If
|
||||
// NetDial is nil, net.Dial is used.
|
||||
NetDial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// Proxy specifies a function to return a proxy for a given
|
||||
// Request. If the function returns a non-nil error, the
|
||||
// request is aborted with the provided error.
|
||||
// If Proxy is nil or returns a nil *URL, no proxy is used.
|
||||
Proxy func(*http.Request) (*url.URL, error)
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||
// If nil, the default configuration is used.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// Subprotocols specifies the client's requested subprotocols.
|
||||
Subprotocols []string
|
||||
|
||||
// EnableCompression specifies if the client should attempt to negotiate
|
||||
// per message compression (RFC 7692). Setting this value to true does not
|
||||
// guarantee that compression will be supported. Currently only "no context
|
||||
// takeover" modes are supported.
|
||||
EnableCompression bool
|
||||
|
||||
// Jar specifies the cookie jar.
|
||||
// If Jar is nil, cookies are not sent in requests and ignored
|
||||
// in responses.
|
||||
Jar http.CookieJar
|
||||
}
|
||||
|
||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||
|
||||
// parseURL parses the URL.
|
||||
//
|
||||
// This function is a replacement for the standard library url.Parse function.
|
||||
// In Go 1.4 and earlier, url.Parse loses information from the path.
|
||||
func parseURL(s string) (*url.URL, error) {
|
||||
// From the RFC:
|
||||
//
|
||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
||||
var u url.URL
|
||||
switch {
|
||||
case strings.HasPrefix(s, "ws://"):
|
||||
u.Scheme = "ws"
|
||||
s = s[len("ws://"):]
|
||||
case strings.HasPrefix(s, "wss://"):
|
||||
u.Scheme = "wss"
|
||||
s = s[len("wss://"):]
|
||||
default:
|
||||
return nil, errMalformedURL
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "?"); i >= 0 {
|
||||
u.RawQuery = s[i+1:]
|
||||
s = s[:i]
|
||||
}
|
||||
|
||||
if i := strings.Index(s, "/"); i >= 0 {
|
||||
u.Opaque = s[i:]
|
||||
s = s[:i]
|
||||
} else {
|
||||
u.Opaque = "/"
|
||||
}
|
||||
|
||||
u.Host = s
|
||||
|
||||
if strings.Contains(u.Host, "@") {
|
||||
// Don't bother parsing user information because user information is
|
||||
// not allowed in websocket URIs.
|
||||
return nil, errMalformedURL
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
hostPort = u.Host
|
||||
hostNoPort = u.Host
|
||||
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
||||
hostNoPort = hostNoPort[:i]
|
||||
} else {
|
||||
switch u.Scheme {
|
||||
case "wss":
|
||||
hostPort += ":443"
|
||||
case "https":
|
||||
hostPort += ":443"
|
||||
default:
|
||||
hostPort += ":80"
|
||||
}
|
||||
}
|
||||
return hostPort, hostNoPort
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
var DefaultDialer = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
// Dial creates a new client connection. Use requestHeader to specify the
|
||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etcetera. The response body may not contain the entire response and does not
|
||||
// need to be closed by the application.
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
|
||||
if d == nil {
|
||||
d = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
}
|
||||
|
||||
challengeKey, err := generateChallengeKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err := parseURL(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "ws":
|
||||
u.Scheme = "http"
|
||||
case "wss":
|
||||
u.Scheme = "https"
|
||||
default:
|
||||
return nil, nil, errMalformedURL
|
||||
}
|
||||
|
||||
if u.User != nil {
|
||||
// User name and password are not allowed in websocket URIs.
|
||||
return nil, nil, errMalformedURL
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
URL: u,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
Host: u.Host,
|
||||
}
|
||||
|
||||
// Set the cookies present in the cookie jar of the dialer
|
||||
if d.Jar != nil {
|
||||
for _, cookie := range d.Jar.Cookies(u) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the request headers using the capitalization for names and values in
|
||||
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||
// servers that depend on it. The Header.Set method is not used because the
|
||||
// method canonicalizes the header names.
|
||||
req.Header["Upgrade"] = []string{"websocket"}
|
||||
req.Header["Connection"] = []string{"Upgrade"}
|
||||
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
|
||||
req.Header["Sec-WebSocket-Version"] = []string{"13"}
|
||||
if len(d.Subprotocols) > 0 {
|
||||
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
|
||||
}
|
||||
for k, vs := range requestHeader {
|
||||
switch {
|
||||
case k == "Host":
|
||||
if len(vs) > 0 {
|
||||
req.Host = vs[0]
|
||||
}
|
||||
case k == "Upgrade" ||
|
||||
k == "Connection" ||
|
||||
k == "Sec-Websocket-Key" ||
|
||||
k == "Sec-Websocket-Version" ||
|
||||
k == "Sec-Websocket-Extensions" ||
|
||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||
default:
|
||||
req.Header[k] = vs
|
||||
}
|
||||
}
|
||||
|
||||
if d.EnableCompression {
|
||||
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
|
||||
}
|
||||
|
||||
hostPort, hostNoPort := hostPortNoPort(u)
|
||||
|
||||
var proxyURL *url.URL
|
||||
// Check wether the proxy method has been configured
|
||||
if d.Proxy != nil {
|
||||
proxyURL, err = d.Proxy(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var targetHostPort string
|
||||
if proxyURL != nil {
|
||||
targetHostPort, _ = hostPortNoPort(proxyURL)
|
||||
} else {
|
||||
targetHostPort = hostPort
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d.HandshakeTimeout != 0 {
|
||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
||||
}
|
||||
|
||||
netDial := d.NetDial
|
||||
if netDial == nil {
|
||||
netDialer := &net.Dialer{Deadline: deadline}
|
||||
netDial = netDialer.Dial
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", targetHostPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if netConn != nil {
|
||||
netConn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err := netConn.SetDeadline(deadline); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if proxyURL != nil {
|
||||
connectHeader := make(http.Header)
|
||||
if user := proxyURL.User; user != nil {
|
||||
proxyUser := user.Username()
|
||||
if proxyPassword, passwordSet := user.Password(); passwordSet {
|
||||
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
|
||||
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
|
||||
}
|
||||
}
|
||||
connectReq := &http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{Opaque: hostPort},
|
||||
Host: hostPort,
|
||||
Header: connectHeader,
|
||||
}
|
||||
|
||||
connectReq.Write(netConn)
|
||||
|
||||
// Read response.
|
||||
// Okay to use and discard buffered reader here, because
|
||||
// TLS server will not speak until spoken to.
|
||||
br := bufio.NewReader(netConn)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
f := strings.SplitN(resp.Status, " ", 2)
|
||||
return nil, nil, errors.New(f[1])
|
||||
}
|
||||
}
|
||||
|
||||
if u.Scheme == "https" {
|
||||
cfg := cloneTLSConfig(d.TLSClientConfig)
|
||||
if cfg.ServerName == "" {
|
||||
cfg.ServerName = hostNoPort
|
||||
}
|
||||
tlsConn := tls.Client(netConn, cfg)
|
||||
netConn = tlsConn
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
||||
|
||||
if err := req.Write(netConn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(conn.br, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if d.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
d.Jar.SetCookies(u, rc)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode != 101 ||
|
||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
||||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
|
||||
// Before closing the network connection on return from this
|
||||
// function, slurp up some of the response to aid application
|
||||
// debugging.
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := io.ReadFull(resp.Body, buf)
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||
return nil, resp, ErrBadHandshake
|
||||
}
|
||||
|
||||
for _, ext := range parseExtensions(resp.Header) {
|
||||
if ext[""] != "permessage-deflate" {
|
||||
continue
|
||||
}
|
||||
_, snct := ext["server_no_context_takeover"]
|
||||
_, cnct := ext["client_no_context_takeover"]
|
||||
if !snct || !cnct {
|
||||
return nil, resp, errInvalidCompression
|
||||
}
|
||||
conn.newCompressionWriter = compressNoContextTakeover
|
||||
conn.newDecompressionReader = decompressNoContextTakeover
|
||||
break
|
||||
}
|
||||
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||
|
||||
netConn.SetDeadline(time.Time{})
|
||||
netConn = nil // to avoid close in defer.
|
||||
return conn, resp, nil
|
||||
}
|
||||
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal file
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return cfg.Clone()
|
||||
}
|
||||
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// cloneTLSConfig clones all public fields except the fields
|
||||
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
|
||||
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
|
||||
// config in active use.
|
||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
}
|
||||
}
|
||||
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
|
||||
maxCompressionLevel = flate.BestCompression
|
||||
defaultCompressionLevel = 1
|
||||
)
|
||||
|
||||
var (
|
||||
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
|
||||
flateReaderPool = sync.Pool{New: func() interface{} {
|
||||
return flate.NewReader(nil)
|
||||
}}
|
||||
)
|
||||
|
||||
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
|
||||
const tail =
|
||||
// Add four bytes as specified in RFC
|
||||
"\x00\x00\xff\xff" +
|
||||
// Add final block to squelch unexpected EOF error from flate reader.
|
||||
"\x01\x00\x00\xff\xff"
|
||||
|
||||
fr, _ := flateReaderPool.Get().(io.ReadCloser)
|
||||
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
|
||||
return &flateReadWrapper{fr}
|
||||
}
|
||||
|
||||
func isValidCompressionLevel(level int) bool {
|
||||
return minCompressionLevel <= level && level <= maxCompressionLevel
|
||||
}
|
||||
|
||||
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
|
||||
p := &flateWriterPools[level-minCompressionLevel]
|
||||
tw := &truncWriter{w: w}
|
||||
fw, _ := p.Get().(*flate.Writer)
|
||||
if fw == nil {
|
||||
fw, _ = flate.NewWriter(tw, level)
|
||||
} else {
|
||||
fw.Reset(tw)
|
||||
}
|
||||
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
|
||||
}
|
||||
|
||||
// truncWriter is an io.Writer that writes all but the last four bytes of the
|
||||
// stream to another io.Writer.
|
||||
type truncWriter struct {
|
||||
w io.WriteCloser
|
||||
n int
|
||||
p [4]byte
|
||||
}
|
||||
|
||||
func (w *truncWriter) Write(p []byte) (int, error) {
|
||||
n := 0
|
||||
|
||||
// fill buffer first for simplicity.
|
||||
if w.n < len(w.p) {
|
||||
n = copy(w.p[w.n:], p)
|
||||
p = p[n:]
|
||||
w.n += n
|
||||
if len(p) == 0 {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
m := len(p)
|
||||
if m > len(w.p) {
|
||||
m = len(w.p)
|
||||
}
|
||||
|
||||
if nn, err := w.w.Write(w.p[:m]); err != nil {
|
||||
return n + nn, err
|
||||
}
|
||||
|
||||
copy(w.p[:], w.p[m:])
|
||||
copy(w.p[len(w.p)-m:], p[len(p)-m:])
|
||||
nn, err := w.w.Write(p[:len(p)-m])
|
||||
return n + nn, err
|
||||
}
|
||||
|
||||
type flateWriteWrapper struct {
|
||||
fw *flate.Writer
|
||||
tw *truncWriter
|
||||
p *sync.Pool
|
||||
}
|
||||
|
||||
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
|
||||
if w.fw == nil {
|
||||
return 0, errWriteClosed
|
||||
}
|
||||
return w.fw.Write(p)
|
||||
}
|
||||
|
||||
func (w *flateWriteWrapper) Close() error {
|
||||
if w.fw == nil {
|
||||
return errWriteClosed
|
||||
}
|
||||
err1 := w.fw.Flush()
|
||||
w.p.Put(w.fw)
|
||||
w.fw = nil
|
||||
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
|
||||
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
|
||||
}
|
||||
err2 := w.tw.w.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
type flateReadWrapper struct {
|
||||
fr io.ReadCloser
|
||||
}
|
||||
|
||||
func (r *flateReadWrapper) Read(p []byte) (int, error) {
|
||||
if r.fr == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
n, err := r.fr.Read(p)
|
||||
if err == io.EOF {
|
||||
// Preemptively place the reader back in the pool. This helps with
|
||||
// scenarios where the application does not call NextReader() soon after
|
||||
// this final read.
|
||||
r.Close()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *flateReadWrapper) Close() error {
|
||||
if r.fr == nil {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
err := r.fr.Close()
|
||||
flateReaderPool.Put(r.fr)
|
||||
r.fr = nil
|
||||
return err
|
||||
}
|
||||
1149
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
1149
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal file
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
return p, err
|
||||
}
|
||||
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
if len(p) > 0 {
|
||||
// advance over the bytes just read
|
||||
io.ReadFull(c.br, p)
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
180
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
180
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package websocket implements the WebSocket protocol defined in RFC 6455.
|
||||
//
|
||||
// Overview
|
||||
//
|
||||
// The Conn type represents a WebSocket connection. A server application uses
|
||||
// the Upgrade function from an Upgrader object with a HTTP request handler
|
||||
// to get a pointer to a Conn:
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// ReadBufferSize: 1024,
|
||||
// WriteBufferSize: 1024,
|
||||
// }
|
||||
//
|
||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// conn, err := upgrader.Upgrade(w, r, nil)
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// ... Use conn to send and receive messages.
|
||||
// }
|
||||
//
|
||||
// Call the connection's WriteMessage and ReadMessage methods to send and
|
||||
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
||||
// messages using these methods:
|
||||
//
|
||||
// for {
|
||||
// messageType, p, err := conn.ReadMessage()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// if err = conn.WriteMessage(messageType, p); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// In above snippet of code, p is a []byte and messageType is an int with value
|
||||
// websocket.BinaryMessage or websocket.TextMessage.
|
||||
//
|
||||
// An application can also send and receive messages using the io.WriteCloser
|
||||
// and io.Reader interfaces. To send a message, call the connection NextWriter
|
||||
// method to get an io.WriteCloser, write the message to the writer and close
|
||||
// the writer when done. To receive a message, call the connection NextReader
|
||||
// method to get an io.Reader and read until io.EOF is returned. This snippet
|
||||
// shows how to echo messages using the NextWriter and NextReader methods:
|
||||
//
|
||||
// for {
|
||||
// messageType, r, err := conn.NextReader()
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// w, err := conn.NextWriter(messageType)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if _, err := io.Copy(w, r); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := w.Close(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Data Messages
|
||||
//
|
||||
// The WebSocket protocol distinguishes between text and binary data messages.
|
||||
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
|
||||
// binary messages is left to the application.
|
||||
//
|
||||
// This package uses the TextMessage and BinaryMessage integer constants to
|
||||
// identify the two data message types. The ReadMessage and NextReader methods
|
||||
// return the type of the received message. The messageType argument to the
|
||||
// WriteMessage and NextWriter methods specifies the type of a sent message.
|
||||
//
|
||||
// It is the application's responsibility to ensure that text messages are
|
||||
// valid UTF-8 encoded text.
|
||||
//
|
||||
// Control Messages
|
||||
//
|
||||
// The WebSocket protocol defines three types of control messages: close, ping
|
||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
|
||||
// methods to send a control message to the peer.
|
||||
//
|
||||
// Connections handle received close messages by sending a close message to the
|
||||
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
|
||||
// message Read method.
|
||||
//
|
||||
// Connections handle received ping and pong messages by invoking callback
|
||||
// functions set with SetPingHandler and SetPongHandler methods. The callback
|
||||
// functions are called from the NextReader, ReadMessage and the message Read
|
||||
// methods.
|
||||
//
|
||||
// The default ping handler sends a pong to the peer. The application's reading
|
||||
// goroutine can block for a short time while the handler writes the pong data
|
||||
// to the connection.
|
||||
//
|
||||
// The application must read the connection to process ping, pong and close
|
||||
// messages sent from the peer. If the application is not otherwise interested
|
||||
// in messages from the peer, then the application should start a goroutine to
|
||||
// read and discard messages from the peer. A simple example is:
|
||||
//
|
||||
// func readLoop(c *websocket.Conn) {
|
||||
// for {
|
||||
// if _, _, err := c.NextReader(); err != nil {
|
||||
// c.Close()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Concurrency
|
||||
//
|
||||
// Connections support one concurrent reader and one concurrent writer.
|
||||
//
|
||||
// Applications are responsible for ensuring that no more than one goroutine
|
||||
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
|
||||
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
|
||||
// that no more than one goroutine calls the read methods (NextReader,
|
||||
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
|
||||
// concurrently.
|
||||
//
|
||||
// The Close and WriteControl methods can be called concurrently with all other
|
||||
// methods.
|
||||
//
|
||||
// Origin Considerations
|
||||
//
|
||||
// Web browsers allow Javascript applications to open a WebSocket connection to
|
||||
// any host. It's up to the server to enforce an origin policy using the Origin
|
||||
// request header sent by the browser.
|
||||
//
|
||||
// The Upgrader calls the function specified in the CheckOrigin field to check
|
||||
// the origin. If the CheckOrigin function returns false, then the Upgrade
|
||||
// method fails the WebSocket handshake with HTTP status 403.
|
||||
//
|
||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
|
||||
// the handshake if the Origin request header is present and not equal to the
|
||||
// Host request header.
|
||||
//
|
||||
// An application can allow connections from any origin by specifying a
|
||||
// function that always returns true:
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// CheckOrigin: func(r *http.Request) bool { return true },
|
||||
// }
|
||||
//
|
||||
// The deprecated Upgrade function does not enforce an origin policy. It's the
|
||||
// application's responsibility to check the Origin header before calling
|
||||
// Upgrade.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||
// by this package in a limited capacity. Setting the EnableCompression option
|
||||
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
|
||||
// support.
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// EnableCompression: true,
|
||||
// }
|
||||
//
|
||||
// If compression was successfully negotiated with the connection's peer, any
|
||||
// message received in compressed form will be automatically decompressed.
|
||||
// All Read methods will return uncompressed bytes.
|
||||
//
|
||||
// Per message compression of messages written to a connection can be enabled
|
||||
// or disabled by calling the corresponding Conn method:
|
||||
//
|
||||
// conn.EnableWriteCompression(false)
|
||||
//
|
||||
// Currently this package does not support compression with "context takeover".
|
||||
// This means that messages must be compressed and decompressed in isolation,
|
||||
// without retaining sliding window or dictionary state across messages. For
|
||||
// more details refer to RFC 7692.
|
||||
//
|
||||
// Use of compression is experimental and may result in decreased performance.
|
||||
package websocket
|
||||
55
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
55
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriteJSON is deprecated, use c.WriteJSON instead.
|
||||
func WriteJSON(c *Conn, v interface{}) error {
|
||||
return c.WriteJSON(v)
|
||||
}
|
||||
|
||||
// WriteJSON writes the JSON encoding of v to the connection.
|
||||
//
|
||||
// See the documentation for encoding/json Marshal for details about the
|
||||
// conversion of Go values to JSON.
|
||||
func (c *Conn) WriteJSON(v interface{}) error {
|
||||
w, err := c.NextWriter(TextMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err1 := json.NewEncoder(w).Encode(v)
|
||||
err2 := w.Close()
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
// ReadJSON is deprecated, use c.ReadJSON instead.
|
||||
func ReadJSON(c *Conn, v interface{}) error {
|
||||
return c.ReadJSON(v)
|
||||
}
|
||||
|
||||
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||
// it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for the encoding/json Unmarshal function for details
|
||||
// about the conversion of JSON to a Go value.
|
||||
func (c *Conn) ReadJSON(v interface{}) error {
|
||||
_, r, err := c.NextReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.NewDecoder(r).Decode(v)
|
||||
if err == io.EOF {
|
||||
// One value is expected in the message.
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
55
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
55
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package websocket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
|
||||
// Mask one byte at a time for small buffers.
|
||||
if len(b) < 2*wordSize {
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
return pos & 3
|
||||
}
|
||||
|
||||
// Mask one byte at a time to word boundary.
|
||||
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
|
||||
n = wordSize - n
|
||||
for i := range b[:n] {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
|
||||
// Create aligned word size key.
|
||||
var k [wordSize]byte
|
||||
for i := range k {
|
||||
k[i] = key[(pos+i)&3]
|
||||
}
|
||||
kw := *(*uintptr)(unsafe.Pointer(&k))
|
||||
|
||||
// Mask one word at a time.
|
||||
n := (len(b) / wordSize) * wordSize
|
||||
for i := 0; i < n; i += wordSize {
|
||||
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
|
||||
}
|
||||
|
||||
// Mask one byte at a time for remaining bytes.
|
||||
b = b[n:]
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
|
||||
return pos & 3
|
||||
}
|
||||
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in the
|
||||
// LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package websocket
|
||||
|
||||
func maskBytes(key [4]byte, pos int, b []byte) int {
|
||||
for i := range b {
|
||||
b[i] ^= key[pos&3]
|
||||
pos++
|
||||
}
|
||||
return pos & 3
|
||||
}
|
||||
103
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal file
103
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PreparedMessage caches on the wire representations of a message payload.
|
||||
// Use PreparedMessage to efficiently send a message payload to multiple
|
||||
// connections. PreparedMessage is especially useful when compression is used
|
||||
// because the CPU and memory expensive compression operation can be executed
|
||||
// once for a given set of compression options.
|
||||
type PreparedMessage struct {
|
||||
messageType int
|
||||
data []byte
|
||||
err error
|
||||
mu sync.Mutex
|
||||
frames map[prepareKey]*preparedFrame
|
||||
}
|
||||
|
||||
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
|
||||
type prepareKey struct {
|
||||
isServer bool
|
||||
compress bool
|
||||
compressionLevel int
|
||||
}
|
||||
|
||||
// preparedFrame contains data in wire representation.
|
||||
type preparedFrame struct {
|
||||
once sync.Once
|
||||
data []byte
|
||||
}
|
||||
|
||||
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
|
||||
// it to connection using WritePreparedMessage method. Valid wire
|
||||
// representation will be calculated lazily only once for a set of current
|
||||
// connection options.
|
||||
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
|
||||
pm := &PreparedMessage{
|
||||
messageType: messageType,
|
||||
frames: make(map[prepareKey]*preparedFrame),
|
||||
data: data,
|
||||
}
|
||||
|
||||
// Prepare a plain server frame.
|
||||
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// To protect against caller modifying the data argument, remember the data
|
||||
// copied to the plain server frame.
|
||||
pm.data = frameData[len(frameData)-len(data):]
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
|
||||
pm.mu.Lock()
|
||||
frame, ok := pm.frames[key]
|
||||
if !ok {
|
||||
frame = &preparedFrame{}
|
||||
pm.frames[key] = frame
|
||||
}
|
||||
pm.mu.Unlock()
|
||||
|
||||
var err error
|
||||
frame.once.Do(func() {
|
||||
// Prepare a frame using a 'fake' connection.
|
||||
// TODO: Refactor code in conn.go to allow more direct construction of
|
||||
// the frame.
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
var nc prepareConn
|
||||
c := &Conn{
|
||||
conn: &nc,
|
||||
mu: mu,
|
||||
isServer: key.isServer,
|
||||
compressionLevel: key.compressionLevel,
|
||||
enableWriteCompression: true,
|
||||
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
|
||||
}
|
||||
if key.compress {
|
||||
c.newCompressionWriter = compressNoContextTakeover
|
||||
}
|
||||
err = c.WriteMessage(pm.messageType, pm.data)
|
||||
frame.data = nc.buf.Bytes()
|
||||
})
|
||||
return pm.messageType, frame.data, err
|
||||
}
|
||||
|
||||
type prepareConn struct {
|
||||
buf bytes.Buffer
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
|
||||
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
291
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
291
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HandshakeError describes an error with the handshake from the peer.
|
||||
type HandshakeError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e HandshakeError) Error() string { return e.message }
|
||||
|
||||
// Upgrader specifies parameters for upgrading an HTTP connection to a
|
||||
// WebSocket connection.
|
||||
type Upgrader struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||
// or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// Subprotocols specifies the server's supported protocols in order of
|
||||
// preference. If this field is set, then the Upgrade method negotiates a
|
||||
// subprotocol by selecting the first match in this list with a protocol
|
||||
// requested by the client.
|
||||
Subprotocols []string
|
||||
|
||||
// Error specifies the function for generating HTTP error responses. If Error
|
||||
// is nil, then http.Error is used to generate the HTTP response.
|
||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
|
||||
|
||||
// CheckOrigin returns true if the request Origin header is acceptable. If
|
||||
// CheckOrigin is nil, the host in the Origin header must not be set or
|
||||
// must match the host of the request.
|
||||
CheckOrigin func(r *http.Request) bool
|
||||
|
||||
// EnableCompression specify if the server should attempt to negotiate per
|
||||
// message compression (RFC 7692). Setting this value to true does not
|
||||
// guarantee that compression will be supported. Currently only "no context
|
||||
// takeover" modes are supported.
|
||||
EnableCompression bool
|
||||
}
|
||||
|
||||
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
|
||||
err := HandshakeError{reason}
|
||||
if u.Error != nil {
|
||||
u.Error(w, r, status, err)
|
||||
} else {
|
||||
w.Header().Set("Sec-Websocket-Version", "13")
|
||||
http.Error(w, http.StatusText(status), status)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
|
||||
func checkSameOrigin(r *http.Request) bool {
|
||||
origin := r.Header["Origin"]
|
||||
if len(origin) == 0 {
|
||||
return true
|
||||
}
|
||||
u, err := url.Parse(origin[0])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return u.Host == r.Host
|
||||
}
|
||||
|
||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||
if u.Subprotocols != nil {
|
||||
clientProtocols := Subprotocols(r)
|
||||
for _, serverProtocol := range u.Subprotocols {
|
||||
for _, clientProtocol := range clientProtocols {
|
||||
if clientProtocol == serverProtocol {
|
||||
return clientProtocol
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if responseHeader != nil {
|
||||
return responseHeader.Get("Sec-Websocket-Protocol")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||
//
|
||||
// The responseHeader is included in the response to the client's upgrade
|
||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
||||
//
|
||||
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
|
||||
// response.
|
||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||
if r.Method != "GET" {
|
||||
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
|
||||
}
|
||||
|
||||
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
|
||||
}
|
||||
|
||||
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
|
||||
}
|
||||
|
||||
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
|
||||
}
|
||||
|
||||
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
|
||||
}
|
||||
|
||||
checkOrigin := u.CheckOrigin
|
||||
if checkOrigin == nil {
|
||||
checkOrigin = checkSameOrigin
|
||||
}
|
||||
if !checkOrigin(r) {
|
||||
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
|
||||
}
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
|
||||
// Negotiate PMCE
|
||||
var compress bool
|
||||
if u.EnableCompression {
|
||||
for _, ext := range parseExtensions(r.Header) {
|
||||
if ext[""] != "permessage-deflate" {
|
||||
continue
|
||||
}
|
||||
compress = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
netConn net.Conn
|
||||
err error
|
||||
)
|
||||
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||
}
|
||||
var brw *bufio.ReadWriter
|
||||
netConn, brw, err = h.Hijack()
|
||||
if err != nil {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if brw.Reader.Buffered() > 0 {
|
||||
netConn.Close()
|
||||
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||
}
|
||||
|
||||
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
|
||||
c.subprotocol = subprotocol
|
||||
|
||||
if compress {
|
||||
c.newCompressionWriter = compressNoContextTakeover
|
||||
c.newDecompressionReader = decompressNoContextTakeover
|
||||
}
|
||||
|
||||
p := c.writeBuf[:0]
|
||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||
p = append(p, computeAcceptKey(challengeKey)...)
|
||||
p = append(p, "\r\n"...)
|
||||
if c.subprotocol != "" {
|
||||
p = append(p, "Sec-Websocket-Protocol: "...)
|
||||
p = append(p, c.subprotocol...)
|
||||
p = append(p, "\r\n"...)
|
||||
}
|
||||
if compress {
|
||||
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
|
||||
}
|
||||
for k, vs := range responseHeader {
|
||||
if k == "Sec-Websocket-Protocol" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vs {
|
||||
p = append(p, k...)
|
||||
p = append(p, ": "...)
|
||||
for i := 0; i < len(v); i++ {
|
||||
b := v[i]
|
||||
if b <= 31 {
|
||||
// prevent response splitting.
|
||||
b = ' '
|
||||
}
|
||||
p = append(p, b)
|
||||
}
|
||||
p = append(p, "\r\n"...)
|
||||
}
|
||||
}
|
||||
p = append(p, "\r\n"...)
|
||||
|
||||
// Clear deadlines set by HTTP server.
|
||||
netConn.SetDeadline(time.Time{})
|
||||
|
||||
if u.HandshakeTimeout > 0 {
|
||||
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
|
||||
}
|
||||
if _, err = netConn.Write(p); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
if u.HandshakeTimeout > 0 {
|
||||
netConn.SetWriteDeadline(time.Time{})
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||
//
|
||||
// This function is deprecated, use websocket.Upgrader instead.
|
||||
//
|
||||
// The application is responsible for checking the request origin before
|
||||
// calling Upgrade. An example implementation of the same origin policy is:
|
||||
//
|
||||
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||
// http.Error(w, "Origin not allowed", 403)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// If the endpoint supports subprotocols, then the application is responsible
|
||||
// for negotiating the protocol used on the connection. Use the Subprotocols()
|
||||
// function to get the subprotocols requested by the client. Use the
|
||||
// Sec-Websocket-Protocol response header to specify the subprotocol selected
|
||||
// by the application.
|
||||
//
|
||||
// The responseHeader is included in the response to the client's upgrade
|
||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||
// negotiated subprotocol (Sec-Websocket-Protocol).
|
||||
//
|
||||
// The connection buffers IO to the underlying network connection. The
|
||||
// readBufSize and writeBufSize parameters specify the size of the buffers to
|
||||
// use. Messages can be larger than the buffers.
|
||||
//
|
||||
// If the request is not a valid WebSocket handshake, then Upgrade returns an
|
||||
// error of type HandshakeError. Applications should handle this error by
|
||||
// replying to the client with an HTTP error response.
|
||||
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
|
||||
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
|
||||
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||
// don't return errors to maintain backwards compatibility
|
||||
}
|
||||
u.CheckOrigin = func(r *http.Request) bool {
|
||||
// allow all connections by default
|
||||
return true
|
||||
}
|
||||
return u.Upgrade(w, r, responseHeader)
|
||||
}
|
||||
|
||||
// Subprotocols returns the subprotocols requested by the client in the
|
||||
// Sec-Websocket-Protocol header.
|
||||
func Subprotocols(r *http.Request) []string {
|
||||
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
|
||||
if h == "" {
|
||||
return nil
|
||||
}
|
||||
protocols := strings.Split(h, ",")
|
||||
for i := range protocols {
|
||||
protocols[i] = strings.TrimSpace(protocols[i])
|
||||
}
|
||||
return protocols
|
||||
}
|
||||
|
||||
// IsWebSocketUpgrade returns true if the client requested upgrade to the
|
||||
// WebSocket protocol.
|
||||
func IsWebSocketUpgrade(r *http.Request) bool {
|
||||
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||
}
|
||||
214
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
214
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||
|
||||
func computeAcceptKey(challengeKey string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(challengeKey))
|
||||
h.Write(keyGUID)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func generateChallengeKey() (string, error) {
|
||||
p := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, p); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(p), nil
|
||||
}
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
var octetTypes [256]byte
|
||||
|
||||
const (
|
||||
isTokenOctet = 1 << iota
|
||||
isSpaceOctet
|
||||
)
|
||||
|
||||
func init() {
|
||||
// From RFC 2616
|
||||
//
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t byte
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpaceOctet
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isTokenOctet
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
func nextToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isTokenOctet == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return nextToken(s)
|
||||
}
|
||||
s = s[1:]
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"':
|
||||
return s[:i], s[i+1:]
|
||||
case '\\':
|
||||
p := make([]byte, len(s)-1)
|
||||
j := copy(p, s[:i])
|
||||
escape := true
|
||||
for i = i + 1; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j += 1
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// tokenListContainsValue returns true if the 1#token header with the given
|
||||
// name contains token.
|
||||
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||
headers:
|
||||
for _, s := range header[name] {
|
||||
for {
|
||||
var t string
|
||||
t, s = nextToken(skipSpace(s))
|
||||
if t == "" {
|
||||
continue headers
|
||||
}
|
||||
s = skipSpace(s)
|
||||
if s != "" && s[0] != ',' {
|
||||
continue headers
|
||||
}
|
||||
if strings.EqualFold(t, value) {
|
||||
return true
|
||||
}
|
||||
if s == "" {
|
||||
continue headers
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseExtensiosn parses WebSocket extensions from a header.
|
||||
func parseExtensions(header http.Header) []map[string]string {
|
||||
|
||||
// From RFC 6455:
|
||||
//
|
||||
// Sec-WebSocket-Extensions = extension-list
|
||||
// extension-list = 1#extension
|
||||
// extension = extension-token *( ";" extension-param )
|
||||
// extension-token = registered-token
|
||||
// registered-token = token
|
||||
// extension-param = token [ "=" (token | quoted-string) ]
|
||||
// ;When using the quoted-string syntax variant, the value
|
||||
// ;after quoted-string unescaping MUST conform to the
|
||||
// ;'token' ABNF.
|
||||
|
||||
var result []map[string]string
|
||||
headers:
|
||||
for _, s := range header["Sec-Websocket-Extensions"] {
|
||||
for {
|
||||
var t string
|
||||
t, s = nextToken(skipSpace(s))
|
||||
if t == "" {
|
||||
continue headers
|
||||
}
|
||||
ext := map[string]string{"": t}
|
||||
for {
|
||||
s = skipSpace(s)
|
||||
if !strings.HasPrefix(s, ";") {
|
||||
break
|
||||
}
|
||||
var k string
|
||||
k, s = nextToken(skipSpace(s[1:]))
|
||||
if k == "" {
|
||||
continue headers
|
||||
}
|
||||
s = skipSpace(s)
|
||||
var v string
|
||||
if strings.HasPrefix(s, "=") {
|
||||
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
|
||||
s = skipSpace(s)
|
||||
}
|
||||
if s != "" && s[0] != ',' && s[0] != ';' {
|
||||
continue headers
|
||||
}
|
||||
ext[k] = v
|
||||
}
|
||||
if s != "" && s[0] != ',' {
|
||||
continue headers
|
||||
}
|
||||
result = append(result, ext)
|
||||
if s == "" {
|
||||
continue headers
|
||||
}
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// DialError is an error that occurs while dialling a websocket server.
|
||||
type DialError struct {
|
||||
*Config
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *DialError) Error() string {
|
||||
return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// NewConfig creates a new WebSocket config for client connection.
|
||||
func NewConfig(server, origin string) (config *Config, err error) {
|
||||
config = new(Config)
|
||||
config.Version = ProtocolVersionHybi13
|
||||
config.Location, err = url.ParseRequestURI(server)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config.Origin, err = url.ParseRequestURI(origin)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config.Header = http.Header(make(map[string][]string))
|
||||
return
|
||||
}
|
||||
|
||||
// NewClient creates a new WebSocket client connection over rwc.
|
||||
func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
err = hybiClientHandshake(config, br, bw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf := bufio.NewReadWriter(br, bw)
|
||||
ws = newHybiClientConn(config, buf, rwc)
|
||||
return
|
||||
}
|
||||
|
||||
// Dial opens a new client connection to a WebSocket.
|
||||
func Dial(url_, protocol, origin string) (ws *Conn, err error) {
|
||||
config, err := NewConfig(url_, origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if protocol != "" {
|
||||
config.Protocol = []string{protocol}
|
||||
}
|
||||
return DialConfig(config)
|
||||
}
|
||||
|
||||
var portMap = map[string]string{
|
||||
"ws": "80",
|
||||
"wss": "443",
|
||||
}
|
||||
|
||||
func parseAuthority(location *url.URL) string {
|
||||
if _, ok := portMap[location.Scheme]; ok {
|
||||
if _, _, err := net.SplitHostPort(location.Host); err != nil {
|
||||
return net.JoinHostPort(location.Host, portMap[location.Scheme])
|
||||
}
|
||||
}
|
||||
return location.Host
|
||||
}
|
||||
|
||||
// DialConfig opens a new client connection to a WebSocket with a config.
|
||||
func DialConfig(config *Config) (ws *Conn, err error) {
|
||||
var client net.Conn
|
||||
if config.Location == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketLocation}
|
||||
}
|
||||
if config.Origin == nil {
|
||||
return nil, &DialError{config, ErrBadWebSocketOrigin}
|
||||
}
|
||||
dialer := config.Dialer
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{}
|
||||
}
|
||||
client, err = dialWithDialer(dialer, config)
|
||||
if err != nil {
|
||||
goto Error
|
||||
}
|
||||
ws, err = NewClient(config, client)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
goto Error
|
||||
}
|
||||
return
|
||||
|
||||
Error:
|
||||
return nil, &DialError{config, err}
|
||||
}
|
||||
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
Normal file
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
|
||||
switch config.Location.Scheme {
|
||||
case "ws":
|
||||
conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
|
||||
|
||||
case "wss":
|
||||
conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
|
||||
|
||||
default:
|
||||
err = ErrBadScheme
|
||||
}
|
||||
return
|
||||
}
|
||||
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
// This file implements a protocol of hybi draft.
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
closeStatusNormal = 1000
|
||||
closeStatusGoingAway = 1001
|
||||
closeStatusProtocolError = 1002
|
||||
closeStatusUnsupportedData = 1003
|
||||
closeStatusFrameTooLarge = 1004
|
||||
closeStatusNoStatusRcvd = 1005
|
||||
closeStatusAbnormalClosure = 1006
|
||||
closeStatusBadMessageData = 1007
|
||||
closeStatusPolicyViolation = 1008
|
||||
closeStatusTooBigData = 1009
|
||||
closeStatusExtensionMismatch = 1010
|
||||
|
||||
maxControlFramePayloadLength = 125
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadMaskingKey = &ProtocolError{"bad masking key"}
|
||||
ErrBadPongMessage = &ProtocolError{"bad pong message"}
|
||||
ErrBadClosingStatus = &ProtocolError{"bad closing status"}
|
||||
ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
|
||||
ErrNotImplemented = &ProtocolError{"not implemented"}
|
||||
|
||||
handshakeHeader = map[string]bool{
|
||||
"Host": true,
|
||||
"Upgrade": true,
|
||||
"Connection": true,
|
||||
"Sec-Websocket-Key": true,
|
||||
"Sec-Websocket-Origin": true,
|
||||
"Sec-Websocket-Version": true,
|
||||
"Sec-Websocket-Protocol": true,
|
||||
"Sec-Websocket-Accept": true,
|
||||
}
|
||||
)
|
||||
|
||||
// A hybiFrameHeader is a frame header as defined in hybi draft.
|
||||
type hybiFrameHeader struct {
|
||||
Fin bool
|
||||
Rsv [3]bool
|
||||
OpCode byte
|
||||
Length int64
|
||||
MaskingKey []byte
|
||||
|
||||
data *bytes.Buffer
|
||||
}
|
||||
|
||||
// A hybiFrameReader is a reader for hybi frame.
|
||||
type hybiFrameReader struct {
|
||||
reader io.Reader
|
||||
|
||||
header hybiFrameHeader
|
||||
pos int64
|
||||
length int
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
|
||||
n, err = frame.reader.Read(msg)
|
||||
if frame.header.MaskingKey != nil {
|
||||
for i := 0; i < n; i++ {
|
||||
msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
|
||||
frame.pos++
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
|
||||
|
||||
func (frame *hybiFrameReader) HeaderReader() io.Reader {
|
||||
if frame.header.data == nil {
|
||||
return nil
|
||||
}
|
||||
if frame.header.data.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return frame.header.data
|
||||
}
|
||||
|
||||
func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
|
||||
|
||||
func (frame *hybiFrameReader) Len() (n int) { return frame.length }
|
||||
|
||||
// A hybiFrameReaderFactory creates new frame reader based on its frame type.
|
||||
type hybiFrameReaderFactory struct {
|
||||
*bufio.Reader
|
||||
}
|
||||
|
||||
// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
|
||||
// See Section 5.2 Base Framing protocol for detail.
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
|
||||
func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
|
||||
hybiFrame := new(hybiFrameReader)
|
||||
frame = hybiFrame
|
||||
var header []byte
|
||||
var b byte
|
||||
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
|
||||
for i := 0; i < 3; i++ {
|
||||
j := uint(6 - i)
|
||||
hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
|
||||
}
|
||||
hybiFrame.header.OpCode = header[0] & 0x0f
|
||||
|
||||
// Second byte. Mask/Payload len(7bits)
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
mask := (b & 0x80) != 0
|
||||
b &= 0x7f
|
||||
lengthFields := 0
|
||||
switch {
|
||||
case b <= 125: // Payload length 7bits.
|
||||
hybiFrame.header.Length = int64(b)
|
||||
case b == 126: // Payload length 7+16bits
|
||||
lengthFields = 2
|
||||
case b == 127: // Payload length 7+64bits
|
||||
lengthFields = 8
|
||||
}
|
||||
for i := 0; i < lengthFields; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
|
||||
b &= 0x7f
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
|
||||
}
|
||||
if mask {
|
||||
// Masking key. 4 bytes.
|
||||
for i := 0; i < 4; i++ {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
header = append(header, b)
|
||||
hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
|
||||
}
|
||||
}
|
||||
hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
|
||||
hybiFrame.header.data = bytes.NewBuffer(header)
|
||||
hybiFrame.length = len(header) + int(hybiFrame.header.Length)
|
||||
return
|
||||
}
|
||||
|
||||
// A HybiFrameWriter is a writer for hybi frame.
|
||||
type hybiFrameWriter struct {
|
||||
writer *bufio.Writer
|
||||
|
||||
header *hybiFrameHeader
|
||||
}
|
||||
|
||||
func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
|
||||
var header []byte
|
||||
var b byte
|
||||
if frame.header.Fin {
|
||||
b |= 0x80
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
if frame.header.Rsv[i] {
|
||||
j := uint(6 - i)
|
||||
b |= 1 << j
|
||||
}
|
||||
}
|
||||
b |= frame.header.OpCode
|
||||
header = append(header, b)
|
||||
if frame.header.MaskingKey != nil {
|
||||
b = 0x80
|
||||
} else {
|
||||
b = 0
|
||||
}
|
||||
lengthFields := 0
|
||||
length := len(msg)
|
||||
switch {
|
||||
case length <= 125:
|
||||
b |= byte(length)
|
||||
case length < 65536:
|
||||
b |= 126
|
||||
lengthFields = 2
|
||||
default:
|
||||
b |= 127
|
||||
lengthFields = 8
|
||||
}
|
||||
header = append(header, b)
|
||||
for i := 0; i < lengthFields; i++ {
|
||||
j := uint((lengthFields - i - 1) * 8)
|
||||
b = byte((length >> j) & 0xff)
|
||||
header = append(header, b)
|
||||
}
|
||||
if frame.header.MaskingKey != nil {
|
||||
if len(frame.header.MaskingKey) != 4 {
|
||||
return 0, ErrBadMaskingKey
|
||||
}
|
||||
header = append(header, frame.header.MaskingKey...)
|
||||
frame.writer.Write(header)
|
||||
data := make([]byte, length)
|
||||
for i := range data {
|
||||
data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
|
||||
}
|
||||
frame.writer.Write(data)
|
||||
err = frame.writer.Flush()
|
||||
return length, err
|
||||
}
|
||||
frame.writer.Write(header)
|
||||
frame.writer.Write(msg)
|
||||
err = frame.writer.Flush()
|
||||
return length, err
|
||||
}
|
||||
|
||||
func (frame *hybiFrameWriter) Close() error { return nil }
|
||||
|
||||
type hybiFrameWriterFactory struct {
|
||||
*bufio.Writer
|
||||
needMaskingKey bool
|
||||
}
|
||||
|
||||
func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
|
||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
|
||||
if buf.needMaskingKey {
|
||||
frameHeader.MaskingKey, err = generateMaskingKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
|
||||
}
|
||||
|
||||
type hybiFrameHandler struct {
|
||||
conn *Conn
|
||||
payloadType byte
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
|
||||
if handler.conn.IsServerConn() {
|
||||
// The client MUST mask all frames sent to the server.
|
||||
if frame.(*hybiFrameReader).header.MaskingKey == nil {
|
||||
handler.WriteClose(closeStatusProtocolError)
|
||||
return nil, io.EOF
|
||||
}
|
||||
} else {
|
||||
// The server MUST NOT mask all frames.
|
||||
if frame.(*hybiFrameReader).header.MaskingKey != nil {
|
||||
handler.WriteClose(closeStatusProtocolError)
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
if header := frame.HeaderReader(); header != nil {
|
||||
io.Copy(ioutil.Discard, header)
|
||||
}
|
||||
switch frame.PayloadType() {
|
||||
case ContinuationFrame:
|
||||
frame.(*hybiFrameReader).header.OpCode = handler.payloadType
|
||||
case TextFrame, BinaryFrame:
|
||||
handler.payloadType = frame.PayloadType()
|
||||
case CloseFrame:
|
||||
return nil, io.EOF
|
||||
case PingFrame, PongFrame:
|
||||
b := make([]byte, maxControlFramePayloadLength)
|
||||
n, err := io.ReadFull(frame, b)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(ioutil.Discard, frame)
|
||||
if frame.PayloadType() == PingFrame {
|
||||
if _, err := handler.WritePong(b[:n]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
|
||||
handler.conn.wio.Lock()
|
||||
defer handler.conn.wio.Unlock()
|
||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(msg, uint16(status))
|
||||
_, err = w.Write(msg)
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
|
||||
handler.conn.wio.Lock()
|
||||
defer handler.conn.wio.Unlock()
|
||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(msg)
|
||||
w.Close()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
|
||||
func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
if buf == nil {
|
||||
br := bufio.NewReader(rwc)
|
||||
bw := bufio.NewWriter(rwc)
|
||||
buf = bufio.NewReadWriter(br, bw)
|
||||
}
|
||||
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
|
||||
frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
|
||||
frameWriterFactory: hybiFrameWriterFactory{
|
||||
buf.Writer, request == nil},
|
||||
PayloadType: TextFrame,
|
||||
defaultCloseStatus: closeStatusNormal}
|
||||
ws.frameHandler = &hybiFrameHandler{conn: ws}
|
||||
return ws
|
||||
}
|
||||
|
||||
// generateMaskingKey generates a masking key for a frame.
|
||||
func generateMaskingKey() (maskingKey []byte, err error) {
|
||||
maskingKey = make([]byte, 4)
|
||||
if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// generateNonce generates a nonce consisting of a randomly selected 16-byte
|
||||
// value that has been base64-encoded.
|
||||
func generateNonce() (nonce []byte) {
|
||||
key := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nonce = make([]byte, 24)
|
||||
base64.StdEncoding.Encode(nonce, key)
|
||||
return
|
||||
}
|
||||
|
||||
// removeZone removes IPv6 zone identifer from host.
|
||||
// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
|
||||
func removeZone(host string) string {
|
||||
if !strings.HasPrefix(host, "[") {
|
||||
return host
|
||||
}
|
||||
i := strings.LastIndex(host, "]")
|
||||
if i < 0 {
|
||||
return host
|
||||
}
|
||||
j := strings.LastIndex(host[:i], "%")
|
||||
if j < 0 {
|
||||
return host
|
||||
}
|
||||
return host[:j] + host[i:]
|
||||
}
|
||||
|
||||
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
|
||||
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
|
||||
func getNonceAccept(nonce []byte) (expected []byte, err error) {
|
||||
h := sha1.New()
|
||||
if _, err = h.Write(nonce); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = h.Write([]byte(websocketGUID)); err != nil {
|
||||
return
|
||||
}
|
||||
expected = make([]byte, 28)
|
||||
base64.StdEncoding.Encode(expected, h.Sum(nil))
|
||||
return
|
||||
}
|
||||
|
||||
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
||||
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
||||
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
||||
|
||||
// According to RFC 6874, an HTTP client, proxy, or other
|
||||
// intermediary must remove any IPv6 zone identifier attached
|
||||
// to an outgoing URI.
|
||||
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
|
||||
bw.WriteString("Upgrade: websocket\r\n")
|
||||
bw.WriteString("Connection: Upgrade\r\n")
|
||||
nonce := generateNonce()
|
||||
if config.handshakeData != nil {
|
||||
nonce = []byte(config.handshakeData["key"])
|
||||
}
|
||||
bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
|
||||
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
|
||||
|
||||
if config.Version != ProtocolVersionHybi13 {
|
||||
return ErrBadProtocolVersion
|
||||
}
|
||||
|
||||
bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
|
||||
if len(config.Protocol) > 0 {
|
||||
bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
|
||||
}
|
||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||
err = config.Header.WriteSubset(bw, handshakeHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bw.WriteString("\r\n")
|
||||
if err = bw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 101 {
|
||||
return ErrBadStatus
|
||||
}
|
||||
if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
|
||||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
||||
return ErrBadUpgrade
|
||||
}
|
||||
expectedAccept, err := getNonceAccept(nonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
|
||||
return ErrChallengeResponse
|
||||
}
|
||||
if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
|
||||
return ErrUnsupportedExtensions
|
||||
}
|
||||
offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
|
||||
if offeredProtocol != "" {
|
||||
protocolMatched := false
|
||||
for i := 0; i < len(config.Protocol); i++ {
|
||||
if config.Protocol[i] == offeredProtocol {
|
||||
protocolMatched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !protocolMatched {
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
config.Protocol = []string{offeredProtocol}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newHybiClientConn creates a client WebSocket connection after handshake.
|
||||
func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
|
||||
return newHybiConn(config, buf, rwc, nil)
|
||||
}
|
||||
|
||||
// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
|
||||
type hybiServerHandshaker struct {
|
||||
*Config
|
||||
accept []byte
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
||||
c.Version = ProtocolVersionHybi13
|
||||
if req.Method != "GET" {
|
||||
return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
||||
}
|
||||
// HTTP version can be safely ignored.
|
||||
|
||||
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
||||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
|
||||
return http.StatusBadRequest, ErrNotWebSocket
|
||||
}
|
||||
|
||||
key := req.Header.Get("Sec-Websocket-Key")
|
||||
if key == "" {
|
||||
return http.StatusBadRequest, ErrChallengeResponse
|
||||
}
|
||||
version := req.Header.Get("Sec-Websocket-Version")
|
||||
switch version {
|
||||
case "13":
|
||||
c.Version = ProtocolVersionHybi13
|
||||
default:
|
||||
return http.StatusBadRequest, ErrBadWebSocketVersion
|
||||
}
|
||||
var scheme string
|
||||
if req.TLS != nil {
|
||||
scheme = "wss"
|
||||
} else {
|
||||
scheme = "ws"
|
||||
}
|
||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
||||
if protocol != "" {
|
||||
protocols := strings.Split(protocol, ",")
|
||||
for i := 0; i < len(protocols); i++ {
|
||||
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
||||
}
|
||||
}
|
||||
c.accept, err = getNonceAccept([]byte(key))
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return http.StatusSwitchingProtocols, nil
|
||||
}
|
||||
|
||||
// Origin parses the Origin header in req.
|
||||
// If the Origin header is not set, it returns nil and nil.
|
||||
func Origin(config *Config, req *http.Request) (*url.URL, error) {
|
||||
var origin string
|
||||
switch config.Version {
|
||||
case ProtocolVersionHybi13:
|
||||
origin = req.Header.Get("Origin")
|
||||
}
|
||||
if origin == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return url.ParseRequestURI(origin)
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
||||
if len(c.Protocol) > 0 {
|
||||
if len(c.Protocol) != 1 {
|
||||
// You need choose a Protocol in Handshake func in Server.
|
||||
return ErrBadWebSocketProtocol
|
||||
}
|
||||
}
|
||||
buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
buf.WriteString("Upgrade: websocket\r\n")
|
||||
buf.WriteString("Connection: Upgrade\r\n")
|
||||
buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
|
||||
if len(c.Protocol) > 0 {
|
||||
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
||||
}
|
||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||
if c.Header != nil {
|
||||
err := c.Header.WriteSubset(buf, handshakeHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
buf.WriteString("\r\n")
|
||||
return buf.Flush()
|
||||
}
|
||||
|
||||
func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
return newHybiServerConn(c.Config, buf, rwc, request)
|
||||
}
|
||||
|
||||
// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
|
||||
func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||
return newHybiConn(config, buf, rwc, request)
|
||||
}
|
||||
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
|
||||
var hs serverHandshaker = &hybiServerHandshaker{Config: config}
|
||||
code, err := hs.ReadHandshake(buf.Reader, req)
|
||||
if err == ErrBadWebSocketVersion {
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
|
||||
buf.WriteString("\r\n")
|
||||
buf.WriteString(err.Error())
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.WriteString(err.Error())
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
if handshake != nil {
|
||||
err = handshake(config, req)
|
||||
if err != nil {
|
||||
code = http.StatusForbidden
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
}
|
||||
err = hs.AcceptHandshake(buf.Writer)
|
||||
if err != nil {
|
||||
code = http.StatusBadRequest
|
||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||
buf.WriteString("\r\n")
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
conn = hs.NewServerConn(buf, rwc, req)
|
||||
return
|
||||
}
|
||||
|
||||
// Server represents a server of a WebSocket.
|
||||
type Server struct {
|
||||
// Config is a WebSocket configuration for new WebSocket connection.
|
||||
Config
|
||||
|
||||
// Handshake is an optional function in WebSocket handshake.
|
||||
// For example, you can check, or don't check Origin header.
|
||||
// Another example, you can select config.Protocol.
|
||||
Handshake func(*Config, *http.Request) error
|
||||
|
||||
// Handler handles a WebSocket connection.
|
||||
Handler
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
s.serveWebSocket(w, req)
|
||||
}
|
||||
|
||||
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
|
||||
rwc, buf, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
panic("Hijack failed: " + err.Error())
|
||||
}
|
||||
// The server should abort the WebSocket connection if it finds
|
||||
// the client did not send a handshake that matches with protocol
|
||||
// specification.
|
||||
defer rwc.Close()
|
||||
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if conn == nil {
|
||||
panic("unexpected nil conn")
|
||||
}
|
||||
s.Handler(conn)
|
||||
}
|
||||
|
||||
// Handler is a simple interface to a WebSocket browser client.
|
||||
// It checks if Origin header is valid URL by default.
|
||||
// You might want to verify websocket.Conn.Config().Origin in the func.
|
||||
// If you use Server instead of Handler, you could call websocket.Origin and
|
||||
// check the origin in your Handshake func. So, if you want to accept
|
||||
// non-browser clients, which do not send an Origin header, set a
|
||||
// Server.Handshake that does not check the origin.
|
||||
type Handler func(*Conn)
|
||||
|
||||
func checkOrigin(config *Config, req *http.Request) (err error) {
|
||||
config.Origin, err = Origin(config, req)
|
||||
if err == nil && config.Origin == nil {
|
||||
return fmt.Errorf("null origin")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
s := Server{Handler: h, Handshake: checkOrigin}
|
||||
s.serveWebSocket(w, req)
|
||||
}
|
||||
448
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
448
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
@@ -0,0 +1,448 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package websocket implements a client and server for the WebSocket protocol
|
||||
// as specified in RFC 6455.
|
||||
//
|
||||
// This package currently lacks some features found in an alternative
|
||||
// and more actively maintained WebSocket package:
|
||||
//
|
||||
// https://godoc.org/github.com/gorilla/websocket
|
||||
//
|
||||
package websocket // import "golang.org/x/net/websocket"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolVersionHybi13 = 13
|
||||
ProtocolVersionHybi = ProtocolVersionHybi13
|
||||
SupportedProtocolVersion = "13"
|
||||
|
||||
ContinuationFrame = 0
|
||||
TextFrame = 1
|
||||
BinaryFrame = 2
|
||||
CloseFrame = 8
|
||||
PingFrame = 9
|
||||
PongFrame = 10
|
||||
UnknownFrame = 255
|
||||
|
||||
DefaultMaxPayloadBytes = 32 << 20 // 32MB
|
||||
)
|
||||
|
||||
// ProtocolError represents WebSocket protocol errors.
|
||||
type ProtocolError struct {
|
||||
ErrorString string
|
||||
}
|
||||
|
||||
func (err *ProtocolError) Error() string { return err.ErrorString }
|
||||
|
||||
var (
|
||||
ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
|
||||
ErrBadScheme = &ProtocolError{"bad scheme"}
|
||||
ErrBadStatus = &ProtocolError{"bad status"}
|
||||
ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"}
|
||||
ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"}
|
||||
ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
|
||||
ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
|
||||
ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
|
||||
ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
|
||||
ErrBadFrame = &ProtocolError{"bad frame"}
|
||||
ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
|
||||
ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
|
||||
ErrBadRequestMethod = &ProtocolError{"bad method"}
|
||||
ErrNotSupported = &ProtocolError{"not supported"}
|
||||
)
|
||||
|
||||
// ErrFrameTooLarge is returned by Codec's Receive method if payload size
|
||||
// exceeds limit set by Conn.MaxPayloadBytes
|
||||
var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit")
|
||||
|
||||
// Addr is an implementation of net.Addr for WebSocket.
|
||||
type Addr struct {
|
||||
*url.URL
|
||||
}
|
||||
|
||||
// Network returns the network type for a WebSocket, "websocket".
|
||||
func (addr *Addr) Network() string { return "websocket" }
|
||||
|
||||
// Config is a WebSocket configuration
|
||||
type Config struct {
|
||||
// A WebSocket server address.
|
||||
Location *url.URL
|
||||
|
||||
// A Websocket client origin.
|
||||
Origin *url.URL
|
||||
|
||||
// WebSocket subprotocols.
|
||||
Protocol []string
|
||||
|
||||
// WebSocket protocol version.
|
||||
Version int
|
||||
|
||||
// TLS config for secure WebSocket (wss).
|
||||
TlsConfig *tls.Config
|
||||
|
||||
// Additional header fields to be sent in WebSocket opening handshake.
|
||||
Header http.Header
|
||||
|
||||
// Dialer used when opening websocket connections.
|
||||
Dialer *net.Dialer
|
||||
|
||||
handshakeData map[string]string
|
||||
}
|
||||
|
||||
// serverHandshaker is an interface to handle WebSocket server side handshake.
|
||||
type serverHandshaker interface {
|
||||
// ReadHandshake reads handshake request message from client.
|
||||
// Returns http response code and error if any.
|
||||
ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
|
||||
|
||||
// AcceptHandshake accepts the client handshake request and sends
|
||||
// handshake response back to client.
|
||||
AcceptHandshake(buf *bufio.Writer) (err error)
|
||||
|
||||
// NewServerConn creates a new WebSocket connection.
|
||||
NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
|
||||
}
|
||||
|
||||
// frameReader is an interface to read a WebSocket frame.
|
||||
type frameReader interface {
|
||||
// Reader is to read payload of the frame.
|
||||
io.Reader
|
||||
|
||||
// PayloadType returns payload type.
|
||||
PayloadType() byte
|
||||
|
||||
// HeaderReader returns a reader to read header of the frame.
|
||||
HeaderReader() io.Reader
|
||||
|
||||
// TrailerReader returns a reader to read trailer of the frame.
|
||||
// If it returns nil, there is no trailer in the frame.
|
||||
TrailerReader() io.Reader
|
||||
|
||||
// Len returns total length of the frame, including header and trailer.
|
||||
Len() int
|
||||
}
|
||||
|
||||
// frameReaderFactory is an interface to creates new frame reader.
|
||||
type frameReaderFactory interface {
|
||||
NewFrameReader() (r frameReader, err error)
|
||||
}
|
||||
|
||||
// frameWriter is an interface to write a WebSocket frame.
|
||||
type frameWriter interface {
|
||||
// Writer is to write payload of the frame.
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
// frameWriterFactory is an interface to create new frame writer.
|
||||
type frameWriterFactory interface {
|
||||
NewFrameWriter(payloadType byte) (w frameWriter, err error)
|
||||
}
|
||||
|
||||
type frameHandler interface {
|
||||
HandleFrame(frame frameReader) (r frameReader, err error)
|
||||
WriteClose(status int) (err error)
|
||||
}
|
||||
|
||||
// Conn represents a WebSocket connection.
|
||||
//
|
||||
// Multiple goroutines may invoke methods on a Conn simultaneously.
|
||||
type Conn struct {
|
||||
config *Config
|
||||
request *http.Request
|
||||
|
||||
buf *bufio.ReadWriter
|
||||
rwc io.ReadWriteCloser
|
||||
|
||||
rio sync.Mutex
|
||||
frameReaderFactory
|
||||
frameReader
|
||||
|
||||
wio sync.Mutex
|
||||
frameWriterFactory
|
||||
|
||||
frameHandler
|
||||
PayloadType byte
|
||||
defaultCloseStatus int
|
||||
|
||||
// MaxPayloadBytes limits the size of frame payload received over Conn
|
||||
// by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used.
|
||||
MaxPayloadBytes int
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface:
|
||||
// it reads data of a frame from the WebSocket connection.
|
||||
// if msg is not large enough for the frame data, it fills the msg and next Read
|
||||
// will read the rest of the frame data.
|
||||
// it reads Text frame or Binary frame.
|
||||
func (ws *Conn) Read(msg []byte) (n int, err error) {
|
||||
ws.rio.Lock()
|
||||
defer ws.rio.Unlock()
|
||||
again:
|
||||
if ws.frameReader == nil {
|
||||
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ws.frameReader == nil {
|
||||
goto again
|
||||
}
|
||||
}
|
||||
n, err = ws.frameReader.Read(msg)
|
||||
if err == io.EOF {
|
||||
if trailer := ws.frameReader.TrailerReader(); trailer != nil {
|
||||
io.Copy(ioutil.Discard, trailer)
|
||||
}
|
||||
ws.frameReader = nil
|
||||
goto again
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface:
|
||||
// it writes data as a frame to the WebSocket connection.
|
||||
func (ws *Conn) Write(msg []byte) (n int, err error) {
|
||||
ws.wio.Lock()
|
||||
defer ws.wio.Unlock()
|
||||
w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err = w.Write(msg)
|
||||
w.Close()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface.
|
||||
func (ws *Conn) Close() error {
|
||||
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
|
||||
err1 := ws.rwc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
func (ws *Conn) IsClientConn() bool { return ws.request == nil }
|
||||
func (ws *Conn) IsServerConn() bool { return ws.request != nil }
|
||||
|
||||
// LocalAddr returns the WebSocket Origin for the connection for client, or
|
||||
// the WebSocket location for server.
|
||||
func (ws *Conn) LocalAddr() net.Addr {
|
||||
if ws.IsClientConn() {
|
||||
return &Addr{ws.config.Origin}
|
||||
}
|
||||
return &Addr{ws.config.Location}
|
||||
}
|
||||
|
||||
// RemoteAddr returns the WebSocket location for the connection for client, or
|
||||
// the Websocket Origin for server.
|
||||
func (ws *Conn) RemoteAddr() net.Addr {
|
||||
if ws.IsClientConn() {
|
||||
return &Addr{ws.config.Location}
|
||||
}
|
||||
return &Addr{ws.config.Origin}
|
||||
}
|
||||
|
||||
var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
|
||||
|
||||
// SetDeadline sets the connection's network read & write deadlines.
|
||||
func (ws *Conn) SetDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the connection's network read deadline.
|
||||
func (ws *Conn) SetReadDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetReadDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the connection's network write deadline.
|
||||
func (ws *Conn) SetWriteDeadline(t time.Time) error {
|
||||
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||
return conn.SetWriteDeadline(t)
|
||||
}
|
||||
return errSetDeadline
|
||||
}
|
||||
|
||||
// Config returns the WebSocket config.
|
||||
func (ws *Conn) Config() *Config { return ws.config }
|
||||
|
||||
// Request returns the http request upgraded to the WebSocket.
|
||||
// It is nil for client side.
|
||||
func (ws *Conn) Request() *http.Request { return ws.request }
|
||||
|
||||
// Codec represents a symmetric pair of functions that implement a codec.
|
||||
type Codec struct {
|
||||
Marshal func(v interface{}) (data []byte, payloadType byte, err error)
|
||||
Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
|
||||
}
|
||||
|
||||
// Send sends v marshaled by cd.Marshal as single frame to ws.
|
||||
func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
|
||||
data, payloadType, err := cd.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.wio.Lock()
|
||||
defer ws.wio.Unlock()
|
||||
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores
|
||||
// in v. The whole frame payload is read to an in-memory buffer; max size of
|
||||
// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds
|
||||
// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire
|
||||
// completely. The next call to Receive would read and discard leftover data of
|
||||
// previous oversized frame before processing next frame.
|
||||
func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
|
||||
ws.rio.Lock()
|
||||
defer ws.rio.Unlock()
|
||||
if ws.frameReader != nil {
|
||||
_, err = io.Copy(ioutil.Discard, ws.frameReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ws.frameReader = nil
|
||||
}
|
||||
again:
|
||||
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
frame, err = ws.frameHandler.HandleFrame(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if frame == nil {
|
||||
goto again
|
||||
}
|
||||
maxPayloadBytes := ws.MaxPayloadBytes
|
||||
if maxPayloadBytes == 0 {
|
||||
maxPayloadBytes = DefaultMaxPayloadBytes
|
||||
}
|
||||
if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) {
|
||||
// payload size exceeds limit, no need to call Unmarshal
|
||||
//
|
||||
// set frameReader to current oversized frame so that
|
||||
// the next call to this function can drain leftover
|
||||
// data before processing the next frame
|
||||
ws.frameReader = frame
|
||||
return ErrFrameTooLarge
|
||||
}
|
||||
payloadType := frame.PayloadType()
|
||||
data, err := ioutil.ReadAll(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cd.Unmarshal(data, payloadType, v)
|
||||
}
|
||||
|
||||
func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||
switch data := v.(type) {
|
||||
case string:
|
||||
return []byte(data), TextFrame, nil
|
||||
case []byte:
|
||||
return data, BinaryFrame, nil
|
||||
}
|
||||
return nil, UnknownFrame, ErrNotSupported
|
||||
}
|
||||
|
||||
func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||
switch data := v.(type) {
|
||||
case *string:
|
||||
*data = string(msg)
|
||||
return nil
|
||||
case *[]byte:
|
||||
*data = msg
|
||||
return nil
|
||||
}
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
/*
|
||||
Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
|
||||
To send/receive text frame, use string type.
|
||||
To send/receive binary frame, use []byte type.
|
||||
|
||||
Trivial usage:
|
||||
|
||||
import "websocket"
|
||||
|
||||
// receive text frame
|
||||
var message string
|
||||
websocket.Message.Receive(ws, &message)
|
||||
|
||||
// send text frame
|
||||
message = "hello"
|
||||
websocket.Message.Send(ws, message)
|
||||
|
||||
// receive binary frame
|
||||
var data []byte
|
||||
websocket.Message.Receive(ws, &data)
|
||||
|
||||
// send binary frame
|
||||
data = []byte{0, 1, 2}
|
||||
websocket.Message.Send(ws, data)
|
||||
|
||||
*/
|
||||
var Message = Codec{marshal, unmarshal}
|
||||
|
||||
func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||
msg, err = json.Marshal(v)
|
||||
return msg, TextFrame, err
|
||||
}
|
||||
|
||||
func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||
return json.Unmarshal(msg, v)
|
||||
}
|
||||
|
||||
/*
|
||||
JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
|
||||
|
||||
Trivial usage:
|
||||
|
||||
import "websocket"
|
||||
|
||||
type T struct {
|
||||
Msg string
|
||||
Count int
|
||||
}
|
||||
|
||||
// receive JSON type T
|
||||
var data T
|
||||
websocket.JSON.Receive(ws, &data)
|
||||
|
||||
// send JSON type T
|
||||
websocket.JSON.Send(ws, data)
|
||||
*/
|
||||
var JSON = Codec{jsonMarshal, jsonUnmarshal}
|
||||
77
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go
generated
vendored
Normal file
77
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/conversion.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internalversion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func Convert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *metav1.ListOptions, s conversion.Scope) error {
|
||||
if err := metav1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := metav1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.IncludeUninitialized = in.IncludeUninitialized
|
||||
out.ResourceVersion = in.ResourceVersion
|
||||
out.TimeoutSeconds = in.TimeoutSeconds
|
||||
out.Watch = in.Watch
|
||||
out.Limit = in.Limit
|
||||
out.Continue = in.Continue
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1_ListOptions_To_internalversion_ListOptions(in *metav1.ListOptions, out *ListOptions, s conversion.Scope) error {
|
||||
if err := metav1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := metav1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.IncludeUninitialized = in.IncludeUninitialized
|
||||
out.ResourceVersion = in.ResourceVersion
|
||||
out.TimeoutSeconds = in.TimeoutSeconds
|
||||
out.Watch = in.Watch
|
||||
out.Limit = in.Limit
|
||||
out.Continue = in.Continue
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_map_to_v1_LabelSelector(in *map[string]string, out *metav1.LabelSelector, s conversion.Scope) error {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out = new(metav1.LabelSelector)
|
||||
for labelKey, labelValue := range *in {
|
||||
metav1.AddLabelToSelector(out, labelKey, labelValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1_LabelSelector_to_map(in *metav1.LabelSelector, out *map[string]string, s conversion.Scope) error {
|
||||
var err error
|
||||
*out, err = metav1.LabelSelectorAsMap(in)
|
||||
if err != nil {
|
||||
err = field.Invalid(field.NewPath("labelSelector"), *in, fmt.Sprintf("cannot convert to old selector: %v", err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
20
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
|
||||
package internalversion
|
||||
105
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/register.go
generated
vendored
Normal file
105
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/register.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internalversion
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
// GroupName is the group name for this API.
|
||||
const GroupName = "meta.k8s.io"
|
||||
|
||||
// Scheme is the registry for any type that adheres to the meta API spec.
|
||||
var scheme = runtime.NewScheme()
|
||||
|
||||
var (
|
||||
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Codecs provides access to encoding and decoding for the scheme.
|
||||
var Codecs = serializer.NewCodecFactory(scheme)
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// ParameterCodec handles versioning of objects that are converted to query parameters.
|
||||
var ParameterCodec = runtime.NewParameterCodec(scheme)
|
||||
|
||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// addToGroupVersion registers common meta types into schemas.
|
||||
func addToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) error {
|
||||
if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil {
|
||||
return err
|
||||
}
|
||||
scheme.AddConversionFuncs(
|
||||
metav1.Convert_string_To_labels_Selector,
|
||||
metav1.Convert_labels_Selector_To_string,
|
||||
|
||||
metav1.Convert_string_To_fields_Selector,
|
||||
metav1.Convert_fields_Selector_To_string,
|
||||
|
||||
Convert_map_to_v1_LabelSelector,
|
||||
Convert_v1_LabelSelector_to_map,
|
||||
|
||||
Convert_internalversion_ListOptions_To_v1_ListOptions,
|
||||
Convert_v1_ListOptions_To_internalversion_ListOptions,
|
||||
)
|
||||
// ListOptions is the only options struct which needs conversion (it exposes labels and fields
|
||||
// as selectors for convenience). The other types have only a single representation today.
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&ListOptions{},
|
||||
&metav1.GetOptions{},
|
||||
&metav1.ExportOptions{},
|
||||
&metav1.DeleteOptions{},
|
||||
)
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&metav1beta1.Table{},
|
||||
&metav1beta1.TableOptions{},
|
||||
&metav1beta1.PartialObjectMetadata{},
|
||||
&metav1beta1.PartialObjectMetadataList{},
|
||||
)
|
||||
scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion,
|
||||
&metav1beta1.Table{},
|
||||
&metav1beta1.TableOptions{},
|
||||
&metav1beta1.PartialObjectMetadata{},
|
||||
&metav1beta1.PartialObjectMetadataList{},
|
||||
)
|
||||
// Allow delete options to be decoded across all version in this scheme (we may want to be more clever than this)
|
||||
scheme.AddUnversionedTypes(SchemeGroupVersion, &metav1.DeleteOptions{})
|
||||
metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlike other API groups, meta internal knows about all meta external versions, but keeps
|
||||
// the logic for conversion private.
|
||||
func init() {
|
||||
if err := addToGroupVersion(scheme, SchemeGroupVersion); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
70
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go
generated
vendored
Normal file
70
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/types.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internalversion
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ListOptions is the query options to a standard REST list call.
|
||||
type ListOptions struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
// A selector based on labels
|
||||
LabelSelector labels.Selector
|
||||
// A selector based on fields
|
||||
FieldSelector fields.Selector
|
||||
// If true, partially initialized resources are included in the response.
|
||||
// +optional
|
||||
IncludeUninitialized bool
|
||||
// If true, watch for changes to this list
|
||||
Watch bool
|
||||
// When specified with a watch call, shows changes that occur after that particular version of a resource.
|
||||
// Defaults to changes from the beginning of history.
|
||||
// When specified for list:
|
||||
// - if unset, then the result is returned from remote storage based on quorum-read flag;
|
||||
// - if it's 0, then we simply return what we currently have in cache, no guarantee;
|
||||
// - if set to non zero, then the result is at least as fresh as given rv.
|
||||
ResourceVersion string
|
||||
// Timeout for the list/watch call.
|
||||
TimeoutSeconds *int64
|
||||
// Limit specifies the maximum number of results to return from the server. The server may
|
||||
// not support this field on all resource types, but if it does and more results remain it
|
||||
// will set the continue field on the returned list object.
|
||||
Limit int64
|
||||
// Continue is a token returned by the server that lets a client retrieve chunks of results
|
||||
// from the server by specifying limit. The server may reject requests for continuation tokens
|
||||
// it does not recognize and will return a 410 error if the token can no longer be used because
|
||||
// it has expired.
|
||||
Continue string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// List holds a list of objects, which may not be known by the server.
|
||||
type List struct {
|
||||
metav1.TypeMeta
|
||||
// +optional
|
||||
metav1.ListMeta
|
||||
|
||||
Items []runtime.Object
|
||||
}
|
||||
118
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go
generated
vendored
Normal file
118
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.conversion.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package internalversion
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(scheme *runtime.Scheme) error {
|
||||
return scheme.AddGeneratedConversionFuncs(
|
||||
Convert_internalversion_List_To_v1_List,
|
||||
Convert_v1_List_To_internalversion_List,
|
||||
Convert_internalversion_ListOptions_To_v1_ListOptions,
|
||||
Convert_v1_ListOptions_To_internalversion_ListOptions,
|
||||
)
|
||||
}
|
||||
|
||||
func autoConvert_internalversion_List_To_v1_List(in *List, out *v1.List, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]runtime.RawExtension, len(*in))
|
||||
for i := range *in {
|
||||
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&(*in)[i], &(*out)[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.Items = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_internalversion_List_To_v1_List is an autogenerated conversion function.
|
||||
func Convert_internalversion_List_To_v1_List(in *List, out *v1.List, s conversion.Scope) error {
|
||||
return autoConvert_internalversion_List_To_v1_List(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_List_To_internalversion_List(in *v1.List, out *List, s conversion.Scope) error {
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]runtime.Object, len(*in))
|
||||
for i := range *in {
|
||||
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&(*in)[i], &(*out)[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.Items = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_List_To_internalversion_List is an autogenerated conversion function.
|
||||
func Convert_v1_List_To_internalversion_List(in *v1.List, out *List, s conversion.Scope) error {
|
||||
return autoConvert_v1_List_To_internalversion_List(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_internalversion_ListOptions_To_v1_ListOptions(in *ListOptions, out *v1.ListOptions, s conversion.Scope) error {
|
||||
if err := v1.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v1.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.IncludeUninitialized = in.IncludeUninitialized
|
||||
out.Watch = in.Watch
|
||||
out.ResourceVersion = in.ResourceVersion
|
||||
out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.Limit = in.Limit
|
||||
out.Continue = in.Continue
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1_ListOptions_To_internalversion_ListOptions(in *v1.ListOptions, out *ListOptions, s conversion.Scope) error {
|
||||
if err := v1.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v1.Convert_string_To_fields_Selector(&in.FieldSelector, &out.FieldSelector, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.IncludeUninitialized = in.IncludeUninitialized
|
||||
out.Watch = in.Watch
|
||||
out.ResourceVersion = in.ResourceVersion
|
||||
out.TimeoutSeconds = (*int64)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.Limit = in.Limit
|
||||
out.Continue = in.Continue
|
||||
return nil
|
||||
}
|
||||
106
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go
generated
vendored
Normal file
106
vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/zz_generated.deepcopy.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package internalversion
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *List) DeepCopyInto(out *List) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]runtime.Object, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] == nil {
|
||||
(*out)[i] = nil
|
||||
} else {
|
||||
(*out)[i] = (*in)[i].DeepCopyObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new List.
|
||||
func (in *List) DeepCopy() *List {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(List)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *List) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ListOptions) DeepCopyInto(out *ListOptions) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.LabelSelector == nil {
|
||||
out.LabelSelector = nil
|
||||
} else {
|
||||
out.LabelSelector = in.LabelSelector.DeepCopySelector()
|
||||
}
|
||||
if in.FieldSelector == nil {
|
||||
out.FieldSelector = nil
|
||||
} else {
|
||||
out.FieldSelector = in.FieldSelector.DeepCopySelector()
|
||||
}
|
||||
if in.TimeoutSeconds != nil {
|
||||
in, out := &in.TimeoutSeconds, &out.TimeoutSeconds
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListOptions.
|
||||
func (in *ListOptions) DeepCopy() *ListOptions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ListOptions)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ListOptions) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
19
vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package httpstream adds multiplexed streaming support to HTTP requests and
|
||||
// responses via connection upgrades.
|
||||
package httpstream // import "k8s.io/apimachinery/pkg/util/httpstream"
|
||||
149
vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go
generated
vendored
Normal file
149
vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package httpstream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderConnection = "Connection"
|
||||
HeaderUpgrade = "Upgrade"
|
||||
HeaderProtocolVersion = "X-Stream-Protocol-Version"
|
||||
HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions"
|
||||
)
|
||||
|
||||
// NewStreamHandler defines a function that is called when a new Stream is
|
||||
// received. If no error is returned, the Stream is accepted; otherwise,
|
||||
// the stream is rejected. After the reply frame has been sent, replySent is closed.
|
||||
type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error
|
||||
|
||||
// NoOpNewStreamHandler is a stream handler that accepts a new stream and
|
||||
// performs no other logic.
|
||||
func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil }
|
||||
|
||||
// Dialer knows how to open a streaming connection to a server.
|
||||
type Dialer interface {
|
||||
|
||||
// Dial opens a streaming connection to a server using one of the protocols
|
||||
// specified (in order of most preferred to least preferred).
|
||||
Dial(protocols ...string) (Connection, string, error)
|
||||
}
|
||||
|
||||
// UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade
|
||||
// HTTP requests to support multiplexed bidirectional streams. After RoundTrip()
|
||||
// is invoked, if the upgrade is successful, clients may retrieve the upgraded
|
||||
// connection by calling UpgradeRoundTripper.Connection().
|
||||
type UpgradeRoundTripper interface {
|
||||
http.RoundTripper
|
||||
// NewConnection validates the response and creates a new Connection.
|
||||
NewConnection(resp *http.Response) (Connection, error)
|
||||
}
|
||||
|
||||
// ResponseUpgrader knows how to upgrade HTTP requests and responses to
|
||||
// add streaming support to them.
|
||||
type ResponseUpgrader interface {
|
||||
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
|
||||
// streams. newStreamHandler will be called asynchronously whenever the
|
||||
// other end of the upgraded connection creates a new stream.
|
||||
UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection
|
||||
}
|
||||
|
||||
// Connection represents an upgraded HTTP connection.
|
||||
type Connection interface {
|
||||
// CreateStream creates a new Stream with the supplied headers.
|
||||
CreateStream(headers http.Header) (Stream, error)
|
||||
// Close resets all streams and closes the connection.
|
||||
Close() error
|
||||
// CloseChan returns a channel that is closed when the underlying connection is closed.
|
||||
CloseChan() <-chan bool
|
||||
// SetIdleTimeout sets the amount of time the connection may remain idle before
|
||||
// it is automatically closed.
|
||||
SetIdleTimeout(timeout time.Duration)
|
||||
}
|
||||
|
||||
// Stream represents a bidirectional communications channel that is part of an
|
||||
// upgraded connection.
|
||||
type Stream interface {
|
||||
io.ReadWriteCloser
|
||||
// Reset closes both directions of the stream, indicating that neither client
|
||||
// or server can use it any more.
|
||||
Reset() error
|
||||
// Headers returns the headers used to create the stream.
|
||||
Headers() http.Header
|
||||
// Identifier returns the stream's ID.
|
||||
Identifier() uint32
|
||||
}
|
||||
|
||||
// IsUpgradeRequest returns true if the given request is a connection upgrade request
|
||||
func IsUpgradeRequest(req *http.Request) bool {
|
||||
for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] {
|
||||
if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func negotiateProtocol(clientProtocols, serverProtocols []string) string {
|
||||
for i := range clientProtocols {
|
||||
for j := range serverProtocols {
|
||||
if clientProtocols[i] == serverProtocols[j] {
|
||||
return clientProtocols[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Handshake performs a subprotocol negotiation. If the client did request a
|
||||
// subprotocol, Handshake will select the first common value found in
|
||||
// serverProtocols. If a match is found, Handshake adds a response header
|
||||
// indicating the chosen subprotocol. If no match is found, HTTP forbidden is
|
||||
// returned, along with a response header containing the list of protocols the
|
||||
// server can accept.
|
||||
func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) {
|
||||
clientProtocols := req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)]
|
||||
if len(clientProtocols) == 0 {
|
||||
// Kube 1.0 clients didn't support subprotocol negotiation.
|
||||
// TODO require clientProtocols once Kube 1.0 is no longer supported
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(serverProtocols) == 0 {
|
||||
// Kube 1.0 servers didn't support subprotocol negotiation. This is mainly for testing.
|
||||
// TODO require serverProtocols once Kube 1.0 is no longer supported
|
||||
return "", nil
|
||||
}
|
||||
|
||||
negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols)
|
||||
if len(negotiatedProtocol) == 0 {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
for i := range serverProtocols {
|
||||
w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i])
|
||||
}
|
||||
fmt.Fprintf(w, "unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols)
|
||||
return "", fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server supports %v", clientProtocols, serverProtocols)
|
||||
}
|
||||
|
||||
w.Header().Add(HeaderProtocolVersion, negotiatedProtocol)
|
||||
return negotiatedProtocol, nil
|
||||
}
|
||||
145
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go
generated
vendored
Normal file
145
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/spdystream"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
// connection maintains state about a spdystream.Connection and its associated
|
||||
// streams.
|
||||
type connection struct {
|
||||
conn *spdystream.Connection
|
||||
streams []httpstream.Stream
|
||||
streamLock sync.Mutex
|
||||
newStreamHandler httpstream.NewStreamHandler
|
||||
}
|
||||
|
||||
// NewClientConnection creates a new SPDY client connection.
|
||||
func NewClientConnection(conn net.Conn) (httpstream.Connection, error) {
|
||||
spdyConn, err := spdystream.NewConnection(conn, false)
|
||||
if err != nil {
|
||||
defer conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newConnection(spdyConn, httpstream.NoOpNewStreamHandler), nil
|
||||
}
|
||||
|
||||
// NewServerConnection creates a new SPDY server connection. newStreamHandler
|
||||
// will be invoked when the server receives a newly created stream from the
|
||||
// client.
|
||||
func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) {
|
||||
spdyConn, err := spdystream.NewConnection(conn, true)
|
||||
if err != nil {
|
||||
defer conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newConnection(spdyConn, newStreamHandler), nil
|
||||
}
|
||||
|
||||
// newConnection returns a new connection wrapping conn. newStreamHandler
|
||||
// will be invoked when the server receives a newly created stream from the
|
||||
// client.
|
||||
func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
|
||||
c := &connection{conn: conn, newStreamHandler: newStreamHandler}
|
||||
go conn.Serve(c.newSpdyStream)
|
||||
return c
|
||||
}
|
||||
|
||||
// createStreamResponseTimeout indicates how long to wait for the other side to
|
||||
// acknowledge the new stream before timing out.
|
||||
const createStreamResponseTimeout = 30 * time.Second
|
||||
|
||||
// Close first sends a reset for all of the connection's streams, and then
|
||||
// closes the underlying spdystream.Connection.
|
||||
func (c *connection) Close() error {
|
||||
c.streamLock.Lock()
|
||||
for _, s := range c.streams {
|
||||
// calling Reset instead of Close ensures that all streams are fully torn down
|
||||
s.Reset()
|
||||
}
|
||||
c.streams = make([]httpstream.Stream, 0)
|
||||
c.streamLock.Unlock()
|
||||
|
||||
// now that all streams are fully torn down, it's safe to call close on the underlying connection,
|
||||
// which should be able to terminate immediately at this point, instead of waiting for any
|
||||
// remaining graceful stream termination.
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// CreateStream creates a new stream with the specified headers and registers
|
||||
// it with the connection.
|
||||
func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) {
|
||||
stream, err := c.conn.CreateStream(headers, nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.registerStream(stream)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// registerStream adds the stream s to the connection's list of streams that
|
||||
// it owns.
|
||||
func (c *connection) registerStream(s httpstream.Stream) {
|
||||
c.streamLock.Lock()
|
||||
c.streams = append(c.streams, s)
|
||||
c.streamLock.Unlock()
|
||||
}
|
||||
|
||||
// CloseChan returns a channel that, when closed, indicates that the underlying
|
||||
// spdystream.Connection has been closed.
|
||||
func (c *connection) CloseChan() <-chan bool {
|
||||
return c.conn.CloseChan()
|
||||
}
|
||||
|
||||
// newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve.
|
||||
// It calls connection's newStreamHandler, giving it the opportunity to accept or reject
|
||||
// the stream. If newStreamHandler returns an error, the stream is rejected. If not, the
|
||||
// stream is accepted and registered with the connection.
|
||||
func (c *connection) newSpdyStream(stream *spdystream.Stream) {
|
||||
replySent := make(chan struct{})
|
||||
err := c.newStreamHandler(stream, replySent)
|
||||
rejectStream := (err != nil)
|
||||
if rejectStream {
|
||||
glog.Warningf("Stream rejected: %v", err)
|
||||
stream.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
c.registerStream(stream)
|
||||
stream.SendReply(http.Header{}, rejectStream)
|
||||
close(replySent)
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the amount of time the connection may remain idle before
|
||||
// it is automatically closed.
|
||||
func (c *connection) SetIdleTimeout(timeout time.Duration) {
|
||||
c.conn.SetIdleTimeout(timeout)
|
||||
}
|
||||
326
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
generated
vendored
Normal file
326
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/third_party/forked/golang/netutil"
|
||||
)
|
||||
|
||||
// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports
|
||||
// multiplexed streams. After RoundTrip() is invoked, Conn will be set
|
||||
// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface.
|
||||
type SpdyRoundTripper struct {
|
||||
//tlsConfig holds the TLS configuration settings to use when connecting
|
||||
//to the remote server.
|
||||
tlsConfig *tls.Config
|
||||
|
||||
/* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper
|
||||
must be safe for use by multiple concurrent goroutines. If this is absolutely
|
||||
necessary, we could keep a map from http.Request to net.Conn. In practice,
|
||||
a client will create an http.Client, set the transport to a new insteace of
|
||||
SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue.
|
||||
*/
|
||||
// conn is the underlying network connection to the remote server.
|
||||
conn net.Conn
|
||||
|
||||
// Dialer is the dialer used to connect. Used if non-nil.
|
||||
Dialer *net.Dialer
|
||||
|
||||
// proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment
|
||||
// Used primarily for mocking the proxy discovery in tests.
|
||||
proxier func(req *http.Request) (*url.URL, error)
|
||||
|
||||
// followRedirects indicates if the round tripper should examine responses for redirects and
|
||||
// follow them.
|
||||
followRedirects bool
|
||||
}
|
||||
|
||||
var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
|
||||
var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{}
|
||||
var _ utilnet.Dialer = &SpdyRoundTripper{}
|
||||
|
||||
// NewRoundTripper creates a new SpdyRoundTripper that will use
|
||||
// the specified tlsConfig.
|
||||
func NewRoundTripper(tlsConfig *tls.Config, followRedirects bool) httpstream.UpgradeRoundTripper {
|
||||
return NewSpdyRoundTripper(tlsConfig, followRedirects)
|
||||
}
|
||||
|
||||
// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use
|
||||
// the specified tlsConfig. This function is mostly meant for unit tests.
|
||||
func NewSpdyRoundTripper(tlsConfig *tls.Config, followRedirects bool) *SpdyRoundTripper {
|
||||
return &SpdyRoundTripper{tlsConfig: tlsConfig, followRedirects: followRedirects}
|
||||
}
|
||||
|
||||
// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
|
||||
// proxying with a spdy roundtripper.
|
||||
func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
|
||||
return s.tlsConfig
|
||||
}
|
||||
|
||||
// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer.
|
||||
func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
|
||||
conn, err := s.dial(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := req.Write(conn); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// dial dials the host specified by req, using TLS if appropriate, optionally
|
||||
// using a proxy server if one is configured via environment variables.
|
||||
func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) {
|
||||
proxier := s.proxier
|
||||
if proxier == nil {
|
||||
proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
|
||||
}
|
||||
proxyURL, err := proxier(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyURL == nil {
|
||||
return s.dialWithoutProxy(req.URL)
|
||||
}
|
||||
|
||||
// ensure we use a canonical host with proxyReq
|
||||
targetHost := netutil.CanonicalAddr(req.URL)
|
||||
|
||||
// proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support
|
||||
proxyReq := http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{},
|
||||
Host: targetHost,
|
||||
}
|
||||
|
||||
if pa := s.proxyAuth(proxyURL); pa != "" {
|
||||
proxyReq.Header = http.Header{}
|
||||
proxyReq.Header.Set("Proxy-Authorization", pa)
|
||||
}
|
||||
|
||||
proxyDialConn, err := s.dialWithoutProxy(proxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil)
|
||||
_, err = proxyClientConn.Do(&proxyReq)
|
||||
if err != nil && err != httputil.ErrPersistEOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rwc, _ := proxyClientConn.Hijack()
|
||||
|
||||
if req.URL.Scheme != "https" {
|
||||
return rwc, nil
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(targetHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := s.tlsConfig
|
||||
switch {
|
||||
case tlsConfig == nil:
|
||||
tlsConfig = &tls.Config{ServerName: host}
|
||||
case len(tlsConfig.ServerName) == 0:
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(rwc, tlsConfig)
|
||||
|
||||
// need to manually call Handshake() so we can call VerifyHostname() below
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return if we were configured to skip validation
|
||||
if tlsConfig.InsecureSkipVerify {
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
if err := tlsConn.VerifyHostname(tlsConfig.ServerName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
// dialWithoutProxy dials the host specified by url, using TLS if appropriate.
|
||||
func (s *SpdyRoundTripper) dialWithoutProxy(url *url.URL) (net.Conn, error) {
|
||||
dialAddr := netutil.CanonicalAddr(url)
|
||||
|
||||
if url.Scheme == "http" {
|
||||
if s.Dialer == nil {
|
||||
return net.Dial("tcp", dialAddr)
|
||||
} else {
|
||||
return s.Dialer.Dial("tcp", dialAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO validate the TLSClientConfig is set up?
|
||||
var conn *tls.Conn
|
||||
var err error
|
||||
if s.Dialer == nil {
|
||||
conn, err = tls.Dial("tcp", dialAddr, s.tlsConfig)
|
||||
} else {
|
||||
conn, err = tls.DialWithDialer(s.Dialer, "tcp", dialAddr, s.tlsConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return if we were configured to skip validation
|
||||
if s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(dialAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.tlsConfig != nil && len(s.tlsConfig.ServerName) > 0 {
|
||||
host = s.tlsConfig.ServerName
|
||||
}
|
||||
err = conn.VerifyHostname(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header
|
||||
func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string {
|
||||
if proxyURL == nil || proxyURL.User == nil {
|
||||
return ""
|
||||
}
|
||||
credentials := proxyURL.User.String()
|
||||
encodedAuth := base64.StdEncoding.EncodeToString([]byte(credentials))
|
||||
return fmt.Sprintf("Basic %s", encodedAuth)
|
||||
}
|
||||
|
||||
// RoundTrip executes the Request and upgrades it. After a successful upgrade,
|
||||
// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded
|
||||
// connection.
|
||||
func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
header := utilnet.CloneHeader(req.Header)
|
||||
header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
|
||||
header.Add(httpstream.HeaderUpgrade, HeaderSpdy31)
|
||||
|
||||
var (
|
||||
conn net.Conn
|
||||
rawResponse []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if s.followRedirects {
|
||||
conn, rawResponse, err = utilnet.ConnectWithRedirects(req.Method, req.URL, header, req.Body, s)
|
||||
} else {
|
||||
clone := utilnet.CloneRequest(req)
|
||||
clone.Header = header
|
||||
conn, err = s.Dial(clone)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseReader := bufio.NewReader(
|
||||
io.MultiReader(
|
||||
bytes.NewBuffer(rawResponse),
|
||||
conn,
|
||||
),
|
||||
)
|
||||
|
||||
resp, err := http.ReadResponse(responseReader, nil)
|
||||
if err != nil {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.conn = conn
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// NewConnection validates the upgrade response, creating and returning a new
|
||||
// httpstream.Connection if there were no errors.
|
||||
func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) {
|
||||
connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection))
|
||||
upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade))
|
||||
if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
|
||||
defer resp.Body.Close()
|
||||
responseError := ""
|
||||
responseErrorBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
responseError = "unable to read error from server response"
|
||||
} else {
|
||||
// TODO: I don't belong here, I should be abstracted from this class
|
||||
if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil {
|
||||
if status, ok := obj.(*metav1.Status); ok {
|
||||
return nil, &apierrors.StatusError{ErrStatus: *status}
|
||||
}
|
||||
}
|
||||
responseError = string(responseErrorBytes)
|
||||
responseError = strings.TrimSpace(responseError)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to upgrade connection: %s", responseError)
|
||||
}
|
||||
|
||||
return NewClientConnection(s.conn)
|
||||
}
|
||||
|
||||
// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection
|
||||
var statusScheme = runtime.NewScheme()
|
||||
|
||||
// ParameterCodec knows about query parameters used with the meta v1 API spec.
|
||||
var statusCodecs = serializer.NewCodecFactory(statusScheme)
|
||||
|
||||
func init() {
|
||||
statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion,
|
||||
&metav1.Status{},
|
||||
)
|
||||
}
|
||||
107
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go
generated
vendored
Normal file
107
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
const HeaderSpdy31 = "SPDY/3.1"
|
||||
|
||||
// responseUpgrader knows how to upgrade HTTP responses. It
|
||||
// implements the httpstream.ResponseUpgrader interface.
|
||||
type responseUpgrader struct {
|
||||
}
|
||||
|
||||
// connWrapper is used to wrap a hijacked connection and its bufio.Reader. All
|
||||
// calls will be handled directly by the underlying net.Conn with the exception
|
||||
// of Read and Close calls, which will consider data in the bufio.Reader. This
|
||||
// ensures that data already inside the used bufio.Reader instance is also
|
||||
// read.
|
||||
type connWrapper struct {
|
||||
net.Conn
|
||||
closed int32
|
||||
bufReader *bufio.Reader
|
||||
}
|
||||
|
||||
func (w *connWrapper) Read(b []byte) (n int, err error) {
|
||||
if atomic.LoadInt32(&w.closed) == 1 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return w.bufReader.Read(b)
|
||||
}
|
||||
|
||||
func (w *connWrapper) Close() error {
|
||||
err := w.Conn.Close()
|
||||
atomic.StoreInt32(&w.closed, 1)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewResponseUpgrader returns a new httpstream.ResponseUpgrader that is
|
||||
// capable of upgrading HTTP responses using SPDY/3.1 via the
|
||||
// spdystream package.
|
||||
func NewResponseUpgrader() httpstream.ResponseUpgrader {
|
||||
return responseUpgrader{}
|
||||
}
|
||||
|
||||
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
|
||||
// streams. newStreamHandler will be called synchronously whenever the
|
||||
// other end of the upgraded connection creates a new stream.
|
||||
func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
|
||||
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
|
||||
upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))
|
||||
if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "unable to upgrade: missing upgrade headers in request: %#v", req.Header)
|
||||
return nil
|
||||
}
|
||||
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "unable to upgrade: unable to hijack response")
|
||||
return nil
|
||||
}
|
||||
|
||||
w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
|
||||
w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
|
||||
conn, bufrw, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
connWithBuf := &connWrapper{Conn: conn, bufReader: bufrw.Reader}
|
||||
spdyConn, err := NewServerConnection(connWithBuf, newStreamHandler)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to upgrade: error creating SPDY server connection: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return spdyConn
|
||||
}
|
||||
53
vendor/k8s.io/apimachinery/pkg/util/remotecommand/constants.go
generated
vendored
Normal file
53
vendor/k8s.io/apimachinery/pkg/util/remotecommand/constants.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultStreamCreationTimeout = 30 * time.Second
|
||||
|
||||
// The SPDY subprotocol "channel.k8s.io" is used for remote command
|
||||
// attachment/execution. This represents the initial unversioned subprotocol,
|
||||
// which has the known bugs http://issues.k8s.io/13394 and
|
||||
// http://issues.k8s.io/13395.
|
||||
StreamProtocolV1Name = "channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v2.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the second version of the subprotocol and
|
||||
// resolves the issues present in the first version.
|
||||
StreamProtocolV2Name = "v2.channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v3.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the third version of the subprotocol and
|
||||
// adds support for resizing container terminals.
|
||||
StreamProtocolV3Name = "v3.channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v4.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the 4th version of the subprotocol and
|
||||
// adds support for exit codes.
|
||||
StreamProtocolV4Name = "v4.channel.k8s.io"
|
||||
|
||||
NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode")
|
||||
ExitCodeCauseType = metav1.CauseType("ExitCode")
|
||||
)
|
||||
|
||||
var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name}
|
||||
27
vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go
generated
vendored
Normal file
27
vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FROM: http://golang.org/src/net/http/client.go
|
||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||
// return true if the string includes a port.
|
||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
||||
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
var portMap = map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
}
|
||||
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
// canonicalAddr returns url.Host but always with a ":port" suffix
|
||||
func CanonicalAddr(url *url.URL) string {
|
||||
addr := url.Host
|
||||
if !hasPort(addr) {
|
||||
return addr + ":" + portMap[url.Scheme]
|
||||
}
|
||||
return addr
|
||||
}
|
||||
202
vendor/k8s.io/apiserver/LICENSE
generated
vendored
Normal file
202
vendor/k8s.io/apiserver/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
19
vendor/k8s.io/apiserver/pkg/server/httplog/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/pkg/server/httplog/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package httplog contains a helper object and functions to maintain a log
|
||||
// along with an http response.
|
||||
package httplog // import "k8s.io/apiserver/pkg/server/httplog"
|
||||
213
vendor/k8s.io/apiserver/pkg/server/httplog/httplog.go
generated
vendored
Normal file
213
vendor/k8s.io/apiserver/pkg/server/httplog/httplog.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package httplog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// StacktracePred returns true if a stacktrace should be logged for this status.
|
||||
type StacktracePred func(httpStatus int) (logStacktrace bool)
|
||||
|
||||
type logger interface {
|
||||
Addf(format string, data ...interface{})
|
||||
}
|
||||
|
||||
// Add a layer on top of ResponseWriter, so we can track latency and error
|
||||
// message sources.
|
||||
//
|
||||
// TODO now that we're using go-restful, we shouldn't need to be wrapping
|
||||
// the http.ResponseWriter. We can recover panics from go-restful, and
|
||||
// the logging value is questionable.
|
||||
type respLogger struct {
|
||||
hijacked bool
|
||||
statusRecorded bool
|
||||
status int
|
||||
statusStack string
|
||||
addedInfo string
|
||||
startTime time.Time
|
||||
|
||||
captureErrorOutput bool
|
||||
|
||||
req *http.Request
|
||||
w http.ResponseWriter
|
||||
|
||||
logStacktracePred StacktracePred
|
||||
}
|
||||
|
||||
// Simple logger that logs immediately when Addf is called
|
||||
type passthroughLogger struct{}
|
||||
|
||||
// Addf logs info immediately.
|
||||
func (passthroughLogger) Addf(format string, data ...interface{}) {
|
||||
glog.V(2).Info(fmt.Sprintf(format, data...))
|
||||
}
|
||||
|
||||
// DefaultStacktracePred is the default implementation of StacktracePred.
|
||||
func DefaultStacktracePred(status int) bool {
|
||||
return (status < http.StatusOK || status >= http.StatusInternalServerError) && status != http.StatusSwitchingProtocols
|
||||
}
|
||||
|
||||
// NewLogged turns a normal response writer into a logged response writer.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// defer NewLogged(req, &w).StacktraceWhen(StatusIsNot(200, 202)).Log()
|
||||
//
|
||||
// (Only the call to Log() is deferred, so you can set everything up in one line!)
|
||||
//
|
||||
// Note that this *changes* your writer, to route response writing actions
|
||||
// through the logger.
|
||||
//
|
||||
// Use LogOf(w).Addf(...) to log something along with the response result.
|
||||
func NewLogged(req *http.Request, w *http.ResponseWriter) *respLogger {
|
||||
if _, ok := (*w).(*respLogger); ok {
|
||||
// Don't double-wrap!
|
||||
panic("multiple NewLogged calls!")
|
||||
}
|
||||
rl := &respLogger{
|
||||
startTime: time.Now(),
|
||||
req: req,
|
||||
w: *w,
|
||||
logStacktracePred: DefaultStacktracePred,
|
||||
}
|
||||
*w = rl // hijack caller's writer!
|
||||
return rl
|
||||
}
|
||||
|
||||
// LogOf returns the logger hiding in w. If there is not an existing logger
|
||||
// then a passthroughLogger will be created which will log to stdout immediately
|
||||
// when Addf is called.
|
||||
func LogOf(req *http.Request, w http.ResponseWriter) logger {
|
||||
if _, exists := w.(*respLogger); !exists {
|
||||
pl := &passthroughLogger{}
|
||||
return pl
|
||||
}
|
||||
if rl, ok := w.(*respLogger); ok {
|
||||
return rl
|
||||
}
|
||||
panic("Unable to find or create the logger!")
|
||||
}
|
||||
|
||||
// Unlogged returns the original ResponseWriter, or w if it is not our inserted logger.
|
||||
func Unlogged(w http.ResponseWriter) http.ResponseWriter {
|
||||
if rl, ok := w.(*respLogger); ok {
|
||||
return rl.w
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// StacktraceWhen sets the stacktrace logging predicate, which decides when to log a stacktrace.
|
||||
// There's a default, so you don't need to call this unless you don't like the default.
|
||||
func (rl *respLogger) StacktraceWhen(pred StacktracePred) *respLogger {
|
||||
rl.logStacktracePred = pred
|
||||
return rl
|
||||
}
|
||||
|
||||
// StatusIsNot returns a StacktracePred which will cause stacktraces to be logged
|
||||
// for any status *not* in the given list.
|
||||
func StatusIsNot(statuses ...int) StacktracePred {
|
||||
return func(status int) bool {
|
||||
for _, s := range statuses {
|
||||
if status == s {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Addf adds additional data to be logged with this request.
|
||||
func (rl *respLogger) Addf(format string, data ...interface{}) {
|
||||
rl.addedInfo += "\n" + fmt.Sprintf(format, data...)
|
||||
}
|
||||
|
||||
// Log is intended to be called once at the end of your request handler, via defer
|
||||
func (rl *respLogger) Log() {
|
||||
latency := time.Since(rl.startTime)
|
||||
if glog.V(3) {
|
||||
if !rl.hijacked {
|
||||
glog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) %v%v%v [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.status, rl.statusStack, rl.addedInfo, rl.req.Header["User-Agent"], rl.req.RemoteAddr))
|
||||
} else {
|
||||
glog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) hijacked [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.req.Header["User-Agent"], rl.req.RemoteAddr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header implements http.ResponseWriter.
|
||||
func (rl *respLogger) Header() http.Header {
|
||||
return rl.w.Header()
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter.
|
||||
func (rl *respLogger) Write(b []byte) (int, error) {
|
||||
if !rl.statusRecorded {
|
||||
rl.recordStatus(http.StatusOK) // Default if WriteHeader hasn't been called
|
||||
}
|
||||
if rl.captureErrorOutput {
|
||||
rl.Addf("logging error output: %q\n", string(b))
|
||||
}
|
||||
return rl.w.Write(b)
|
||||
}
|
||||
|
||||
// Flush implements http.Flusher even if the underlying http.Writer doesn't implement it.
|
||||
// Flush is used for streaming purposes and allows to flush buffered data to the client.
|
||||
func (rl *respLogger) Flush() {
|
||||
if flusher, ok := rl.w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
} else if glog.V(2) {
|
||||
glog.InfoDepth(1, fmt.Sprintf("Unable to convert %+v into http.Flusher", rl.w))
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader implements http.ResponseWriter.
|
||||
func (rl *respLogger) WriteHeader(status int) {
|
||||
rl.recordStatus(status)
|
||||
rl.w.WriteHeader(status)
|
||||
}
|
||||
|
||||
// Hijack implements http.Hijacker.
|
||||
func (rl *respLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
rl.hijacked = true
|
||||
return rl.w.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements http.CloseNotifier
|
||||
func (rl *respLogger) CloseNotify() <-chan bool {
|
||||
return rl.w.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (rl *respLogger) recordStatus(status int) {
|
||||
rl.status = status
|
||||
rl.statusRecorded = true
|
||||
if rl.logStacktracePred(status) {
|
||||
// Only log stacks for errors
|
||||
stack := make([]byte, 50*1024)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
rl.statusStack = "\n" + string(stack)
|
||||
rl.captureErrorOutput = true
|
||||
} else {
|
||||
rl.statusStack = ""
|
||||
}
|
||||
}
|
||||
352
vendor/k8s.io/apiserver/pkg/util/wsstream/conn.go
generated
vendored
Normal file
352
vendor/k8s.io/apiserver/pkg/util/wsstream/conn.go
generated
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package wsstream
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// The Websocket subprotocol "channel.k8s.io" prepends each binary message with a byte indicating
|
||||
// the channel number (zero indexed) the message was sent on. Messages in both directions should
|
||||
// prefix their messages with this channel byte. When used for remote execution, the channel numbers
|
||||
// are by convention defined to match the POSIX file-descriptors assigned to STDIN, STDOUT, and STDERR
|
||||
// (0, 1, and 2). No other conversion is performed on the raw subprotocol - writes are sent as they
|
||||
// are received by the server.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "channel.k8s.io"
|
||||
// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN)
|
||||
// READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT)
|
||||
// CLOSE
|
||||
//
|
||||
const ChannelWebSocketProtocol = "channel.k8s.io"
|
||||
|
||||
// The Websocket subprotocol "base64.channel.k8s.io" base64 encodes each message with a character
|
||||
// indicating the channel number (zero indexed) the message was sent on. Messages in both directions
|
||||
// should prefix their messages with this channel char. When used for remote execution, the channel
|
||||
// numbers are by convention defined to match the POSIX file-descriptors assigned to STDIN, STDOUT,
|
||||
// and STDERR ('0', '1', and '2'). The data received on the server is base64 decoded (and must be
|
||||
// be valid) and data written by the server to the client is base64 encoded.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "base64.channel.k8s.io"
|
||||
// WRITE []byte{48, 90, 109, 57, 118, 67, 103, 111, 61} # send "foo\n" (base64: "Zm9vCgo=") on channel '0' (STDIN)
|
||||
// READ []byte{49, 67, 103, 61, 61} # receive "\n" (base64: "Cg==") on channel '1' (STDOUT)
|
||||
// CLOSE
|
||||
//
|
||||
const Base64ChannelWebSocketProtocol = "base64.channel.k8s.io"
|
||||
|
||||
type codecType int
|
||||
|
||||
const (
|
||||
rawCodec codecType = iota
|
||||
base64Codec
|
||||
)
|
||||
|
||||
type ChannelType int
|
||||
|
||||
const (
|
||||
IgnoreChannel ChannelType = iota
|
||||
ReadChannel
|
||||
WriteChannel
|
||||
ReadWriteChannel
|
||||
)
|
||||
|
||||
var (
|
||||
// connectionUpgradeRegex matches any Connection header value that includes upgrade
|
||||
connectionUpgradeRegex = regexp.MustCompile("(^|.*,\\s*)upgrade($|\\s*,)")
|
||||
)
|
||||
|
||||
// IsWebSocketRequest returns true if the incoming request contains connection upgrade headers
|
||||
// for WebSockets.
|
||||
func IsWebSocketRequest(req *http.Request) bool {
|
||||
if !strings.EqualFold(req.Header.Get("Upgrade"), "websocket") {
|
||||
return false
|
||||
}
|
||||
return connectionUpgradeRegex.MatchString(strings.ToLower(req.Header.Get("Connection")))
|
||||
}
|
||||
|
||||
// IgnoreReceives reads from a WebSocket until it is closed, then returns. If timeout is set, the
|
||||
// read and write deadlines are pushed every time a new message is received.
|
||||
func IgnoreReceives(ws *websocket.Conn, timeout time.Duration) {
|
||||
defer runtime.HandleCrash()
|
||||
var data []byte
|
||||
for {
|
||||
resetTimeout(ws, timeout)
|
||||
if err := websocket.Message.Receive(ws, &data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handshake ensures the provided user protocol matches one of the allowed protocols. It returns
|
||||
// no error if no protocol is specified.
|
||||
func handshake(config *websocket.Config, req *http.Request, allowed []string) error {
|
||||
protocols := config.Protocol
|
||||
if len(protocols) == 0 {
|
||||
protocols = []string{""}
|
||||
}
|
||||
|
||||
for _, protocol := range protocols {
|
||||
for _, allow := range allowed {
|
||||
if allow == protocol {
|
||||
config.Protocol = []string{protocol}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("requested protocol(s) are not supported: %v; supports %v", config.Protocol, allowed)
|
||||
}
|
||||
|
||||
// ChannelProtocolConfig describes a websocket subprotocol with channels.
|
||||
type ChannelProtocolConfig struct {
|
||||
Binary bool
|
||||
Channels []ChannelType
|
||||
}
|
||||
|
||||
// NewDefaultChannelProtocols returns a channel protocol map with the
|
||||
// subprotocols "", "channel.k8s.io", "base64.channel.k8s.io" and the given
|
||||
// channels.
|
||||
func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig {
|
||||
return map[string]ChannelProtocolConfig{
|
||||
"": {Binary: true, Channels: channels},
|
||||
ChannelWebSocketProtocol: {Binary: true, Channels: channels},
|
||||
Base64ChannelWebSocketProtocol: {Binary: false, Channels: channels},
|
||||
}
|
||||
}
|
||||
|
||||
// Conn supports sending multiple binary channels over a websocket connection.
|
||||
type Conn struct {
|
||||
protocols map[string]ChannelProtocolConfig
|
||||
selectedProtocol string
|
||||
channels []*websocketChannel
|
||||
codec codecType
|
||||
ready chan struct{}
|
||||
ws *websocket.Conn
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewConn creates a WebSocket connection that supports a set of channels. Channels begin each
|
||||
// web socket message with a single byte indicating the channel number (0-N). 255 is reserved for
|
||||
// future use. The channel types for each channel are passed as an array, supporting the different
|
||||
// duplex modes. Read and Write refer to whether the channel can be used as a Reader or Writer.
|
||||
//
|
||||
// The protocols parameter maps subprotocol names to ChannelProtocols. The empty string subprotocol
|
||||
// name is used if websocket.Config.Protocol is empty.
|
||||
func NewConn(protocols map[string]ChannelProtocolConfig) *Conn {
|
||||
return &Conn{
|
||||
ready: make(chan struct{}),
|
||||
protocols: protocols,
|
||||
}
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the interval for both reads and writes before timeout. If not specified,
|
||||
// there is no timeout on the connection.
|
||||
func (conn *Conn) SetIdleTimeout(duration time.Duration) {
|
||||
conn.timeout = duration
|
||||
}
|
||||
|
||||
// Open the connection and create channels for reading and writing. It returns
|
||||
// the selected subprotocol, a slice of channels and an error.
|
||||
func (conn *Conn) Open(w http.ResponseWriter, req *http.Request) (string, []io.ReadWriteCloser, error) {
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer conn.Close()
|
||||
websocket.Server{Handshake: conn.handshake, Handler: conn.handle}.ServeHTTP(w, req)
|
||||
}()
|
||||
<-conn.ready
|
||||
rwc := make([]io.ReadWriteCloser, len(conn.channels))
|
||||
for i := range conn.channels {
|
||||
rwc[i] = conn.channels[i]
|
||||
}
|
||||
return conn.selectedProtocol, rwc, nil
|
||||
}
|
||||
|
||||
func (conn *Conn) initialize(ws *websocket.Conn) {
|
||||
negotiated := ws.Config().Protocol
|
||||
conn.selectedProtocol = negotiated[0]
|
||||
p := conn.protocols[conn.selectedProtocol]
|
||||
if p.Binary {
|
||||
conn.codec = rawCodec
|
||||
} else {
|
||||
conn.codec = base64Codec
|
||||
}
|
||||
conn.ws = ws
|
||||
conn.channels = make([]*websocketChannel, len(p.Channels))
|
||||
for i, t := range p.Channels {
|
||||
switch t {
|
||||
case ReadChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), true, false)
|
||||
case WriteChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), false, true)
|
||||
case ReadWriteChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), true, true)
|
||||
case IgnoreChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), false, false)
|
||||
}
|
||||
}
|
||||
|
||||
close(conn.ready)
|
||||
}
|
||||
|
||||
func (conn *Conn) handshake(config *websocket.Config, req *http.Request) error {
|
||||
supportedProtocols := make([]string, 0, len(conn.protocols))
|
||||
for p := range conn.protocols {
|
||||
supportedProtocols = append(supportedProtocols, p)
|
||||
}
|
||||
return handshake(config, req, supportedProtocols)
|
||||
}
|
||||
|
||||
func (conn *Conn) resetTimeout() {
|
||||
if conn.timeout > 0 {
|
||||
conn.ws.SetDeadline(time.Now().Add(conn.timeout))
|
||||
}
|
||||
}
|
||||
|
||||
// Close is only valid after Open has been called
|
||||
func (conn *Conn) Close() error {
|
||||
<-conn.ready
|
||||
for _, s := range conn.channels {
|
||||
s.Close()
|
||||
}
|
||||
conn.ws.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle implements a websocket handler.
|
||||
func (conn *Conn) handle(ws *websocket.Conn) {
|
||||
defer conn.Close()
|
||||
conn.initialize(ws)
|
||||
|
||||
for {
|
||||
conn.resetTimeout()
|
||||
var data []byte
|
||||
if err := websocket.Message.Receive(ws, &data); err != nil {
|
||||
if err != io.EOF {
|
||||
glog.Errorf("Error on socket receive: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
channel := data[0]
|
||||
if conn.codec == base64Codec {
|
||||
channel = channel - '0'
|
||||
}
|
||||
data = data[1:]
|
||||
if int(channel) >= len(conn.channels) {
|
||||
glog.V(6).Infof("Frame is targeted for a reader %d that is not valid, possible protocol error", channel)
|
||||
continue
|
||||
}
|
||||
if _, err := conn.channels[channel].DataFromSocket(data); err != nil {
|
||||
glog.Errorf("Unable to write frame to %d: %v\n%s", channel, err, string(data))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write multiplexes the specified channel onto the websocket
|
||||
func (conn *Conn) write(num byte, data []byte) (int, error) {
|
||||
conn.resetTimeout()
|
||||
switch conn.codec {
|
||||
case rawCodec:
|
||||
frame := make([]byte, len(data)+1)
|
||||
frame[0] = num
|
||||
copy(frame[1:], data)
|
||||
if err := websocket.Message.Send(conn.ws, frame); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case base64Codec:
|
||||
frame := string('0'+num) + base64.StdEncoding.EncodeToString(data)
|
||||
if err := websocket.Message.Send(conn.ws, frame); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// websocketChannel represents a channel in a connection
|
||||
type websocketChannel struct {
|
||||
conn *Conn
|
||||
num byte
|
||||
r io.Reader
|
||||
w io.WriteCloser
|
||||
|
||||
read, write bool
|
||||
}
|
||||
|
||||
// newWebsocketChannel creates a pipe for writing to a websocket. Do not write to this pipe
|
||||
// prior to the connection being opened. It may be no, half, or full duplex depending on
|
||||
// read and write.
|
||||
func newWebsocketChannel(conn *Conn, num byte, read, write bool) *websocketChannel {
|
||||
r, w := io.Pipe()
|
||||
return &websocketChannel{conn, num, r, w, read, write}
|
||||
}
|
||||
|
||||
func (p *websocketChannel) Write(data []byte) (int, error) {
|
||||
if !p.write {
|
||||
return len(data), nil
|
||||
}
|
||||
return p.conn.write(p.num, data)
|
||||
}
|
||||
|
||||
// DataFromSocket is invoked by the connection receiver to move data from the connection
|
||||
// into a specific channel.
|
||||
func (p *websocketChannel) DataFromSocket(data []byte) (int, error) {
|
||||
if !p.read {
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
switch p.conn.codec {
|
||||
case rawCodec:
|
||||
return p.w.Write(data)
|
||||
case base64Codec:
|
||||
dst := make([]byte, len(data))
|
||||
n, err := base64.StdEncoding.Decode(dst, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.w.Write(dst[:n])
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (p *websocketChannel) Read(data []byte) (int, error) {
|
||||
if !p.read {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return p.r.Read(data)
|
||||
}
|
||||
|
||||
func (p *websocketChannel) Close() error {
|
||||
return p.w.Close()
|
||||
}
|
||||
21
vendor/k8s.io/apiserver/pkg/util/wsstream/doc.go
generated
vendored
Normal file
21
vendor/k8s.io/apiserver/pkg/util/wsstream/doc.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package wsstream contains utilities for streaming content over WebSockets.
|
||||
// The Conn type allows callers to multiplex multiple read/write channels over
|
||||
// a single websocket. The Reader type allows an io.Reader to be copied over
|
||||
// a websocket channel as binary content.
|
||||
package wsstream // import "k8s.io/apiserver/pkg/util/wsstream"
|
||||
177
vendor/k8s.io/apiserver/pkg/util/wsstream/stream.go
generated
vendored
Normal file
177
vendor/k8s.io/apiserver/pkg/util/wsstream/stream.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package wsstream
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// The WebSocket subprotocol "binary.k8s.io" will only send messages to the
|
||||
// client and ignore messages sent to the server. The received messages are
|
||||
// the exact bytes written to the stream. Zero byte messages are possible.
|
||||
const binaryWebSocketProtocol = "binary.k8s.io"
|
||||
|
||||
// The WebSocket subprotocol "base64.binary.k8s.io" will only send messages to the
|
||||
// client and ignore messages sent to the server. The received messages are
|
||||
// a base64 version of the bytes written to the stream. Zero byte messages are
|
||||
// possible.
|
||||
const base64BinaryWebSocketProtocol = "base64.binary.k8s.io"
|
||||
|
||||
// ReaderProtocolConfig describes a websocket subprotocol with one stream.
|
||||
type ReaderProtocolConfig struct {
|
||||
Binary bool
|
||||
}
|
||||
|
||||
// NewDefaultReaderProtocols returns a stream protocol map with the
|
||||
// subprotocols "", "channel.k8s.io", "base64.channel.k8s.io".
|
||||
func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig {
|
||||
return map[string]ReaderProtocolConfig{
|
||||
"": {Binary: true},
|
||||
binaryWebSocketProtocol: {Binary: true},
|
||||
base64BinaryWebSocketProtocol: {Binary: false},
|
||||
}
|
||||
}
|
||||
|
||||
// Reader supports returning an arbitrary byte stream over a websocket channel.
|
||||
type Reader struct {
|
||||
err chan error
|
||||
r io.Reader
|
||||
ping bool
|
||||
timeout time.Duration
|
||||
protocols map[string]ReaderProtocolConfig
|
||||
selectedProtocol string
|
||||
|
||||
handleCrash func() // overridable for testing
|
||||
}
|
||||
|
||||
// NewReader creates a WebSocket pipe that will copy the contents of r to a provided
|
||||
// WebSocket connection. If ping is true, a zero length message will be sent to the client
|
||||
// before the stream begins reading.
|
||||
//
|
||||
// The protocols parameter maps subprotocol names to StreamProtocols. The empty string
|
||||
// subprotocol name is used if websocket.Config.Protocol is empty.
|
||||
func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
err: make(chan error),
|
||||
ping: ping,
|
||||
protocols: protocols,
|
||||
handleCrash: func() { runtime.HandleCrash() },
|
||||
}
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the interval for both reads and writes before timeout. If not specified,
|
||||
// there is no timeout on the reader.
|
||||
func (r *Reader) SetIdleTimeout(duration time.Duration) {
|
||||
r.timeout = duration
|
||||
}
|
||||
|
||||
func (r *Reader) handshake(config *websocket.Config, req *http.Request) error {
|
||||
supportedProtocols := make([]string, 0, len(r.protocols))
|
||||
for p := range r.protocols {
|
||||
supportedProtocols = append(supportedProtocols, p)
|
||||
}
|
||||
return handshake(config, req, supportedProtocols)
|
||||
}
|
||||
|
||||
// Copy the reader to the response. The created WebSocket is closed after this
|
||||
// method completes.
|
||||
func (r *Reader) Copy(w http.ResponseWriter, req *http.Request) error {
|
||||
go func() {
|
||||
defer r.handleCrash()
|
||||
websocket.Server{Handshake: r.handshake, Handler: r.handle}.ServeHTTP(w, req)
|
||||
}()
|
||||
return <-r.err
|
||||
}
|
||||
|
||||
// handle implements a WebSocket handler.
|
||||
func (r *Reader) handle(ws *websocket.Conn) {
|
||||
// Close the connection when the client requests it, or when we finish streaming, whichever happens first
|
||||
closeConnOnce := &sync.Once{}
|
||||
closeConn := func() {
|
||||
closeConnOnce.Do(func() {
|
||||
ws.Close()
|
||||
})
|
||||
}
|
||||
|
||||
negotiated := ws.Config().Protocol
|
||||
r.selectedProtocol = negotiated[0]
|
||||
defer close(r.err)
|
||||
defer closeConn()
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
// This blocks until the connection is closed.
|
||||
// Client should not send anything.
|
||||
IgnoreReceives(ws, r.timeout)
|
||||
// Once the client closes, we should also close
|
||||
closeConn()
|
||||
}()
|
||||
|
||||
r.err <- messageCopy(ws, r.r, !r.protocols[r.selectedProtocol].Binary, r.ping, r.timeout)
|
||||
}
|
||||
|
||||
func resetTimeout(ws *websocket.Conn, timeout time.Duration) {
|
||||
if timeout > 0 {
|
||||
ws.SetDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
}
|
||||
|
||||
func messageCopy(ws *websocket.Conn, r io.Reader, base64Encode, ping bool, timeout time.Duration) error {
|
||||
buf := make([]byte, 2048)
|
||||
if ping {
|
||||
resetTimeout(ws, timeout)
|
||||
if base64Encode {
|
||||
if err := websocket.Message.Send(ws, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := websocket.Message.Send(ws, []byte{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
resetTimeout(ws, timeout)
|
||||
n, err := r.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
if base64Encode {
|
||||
if err := websocket.Message.Send(ws, base64.StdEncoding.EncodeToString(buf[:n])); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := websocket.Message.Send(ws, buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
vendor/k8s.io/client-go/tools/remotecommand/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/client-go/tools/remotecommand/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package remotecommand adds support for executing commands in containers,
|
||||
// with support for separate stdin, stdout, and stderr streams, as well as
|
||||
// TTY.
|
||||
package remotecommand // import "k8s.io/client-go/tools/remotecommand"
|
||||
55
vendor/k8s.io/client-go/tools/remotecommand/errorstream.go
generated
vendored
Normal file
55
vendor/k8s.io/client-go/tools/remotecommand/errorstream.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// errorStreamDecoder interprets the data on the error channel and creates a go error object from it.
|
||||
type errorStreamDecoder interface {
|
||||
decode(message []byte) error
|
||||
}
|
||||
|
||||
// watchErrorStream watches the errorStream for remote command error data,
|
||||
// decodes it with the given errorStreamDecoder, sends the decoded error (or nil if the remote
|
||||
// command exited successfully) to the returned error channel, and closes it.
|
||||
// This function returns immediately.
|
||||
func watchErrorStream(errorStream io.Reader, d errorStreamDecoder) chan error {
|
||||
errorChan := make(chan error)
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
message, err := ioutil.ReadAll(errorStream)
|
||||
switch {
|
||||
case err != nil && err != io.EOF:
|
||||
errorChan <- fmt.Errorf("error reading from error stream: %s", err)
|
||||
case len(message) > 0:
|
||||
errorChan <- d.decode(message)
|
||||
default:
|
||||
errorChan <- nil
|
||||
}
|
||||
close(errorChan)
|
||||
}()
|
||||
|
||||
return errorChan
|
||||
}
|
||||
142
vendor/k8s.io/client-go/tools/remotecommand/remotecommand.go
generated
vendored
Normal file
142
vendor/k8s.io/client-go/tools/remotecommand/remotecommand.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
spdy "k8s.io/client-go/transport/spdy"
|
||||
)
|
||||
|
||||
// StreamOptions holds information pertaining to the current streaming session: supported stream
|
||||
// protocols, input/output streams, if the client is requesting a TTY, and a terminal size queue to
|
||||
// support terminal resizing.
|
||||
type StreamOptions struct {
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Tty bool
|
||||
TerminalSizeQueue TerminalSizeQueue
|
||||
}
|
||||
|
||||
// Executor is an interface for transporting shell-style streams.
|
||||
type Executor interface {
|
||||
// Stream initiates the transport of the standard shell streams. It will transport any
|
||||
// non-nil stream to a remote system, and return an error if a problem occurs. If tty
|
||||
// is set, the stderr stream is not used (raw TTY manages stdout and stderr over the
|
||||
// stdout stream).
|
||||
Stream(options StreamOptions) error
|
||||
}
|
||||
|
||||
type streamCreator interface {
|
||||
CreateStream(headers http.Header) (httpstream.Stream, error)
|
||||
}
|
||||
|
||||
type streamProtocolHandler interface {
|
||||
stream(conn streamCreator) error
|
||||
}
|
||||
|
||||
// streamExecutor handles transporting standard shell streams over an httpstream connection.
|
||||
type streamExecutor struct {
|
||||
upgrader spdy.Upgrader
|
||||
transport http.RoundTripper
|
||||
|
||||
method string
|
||||
url *url.URL
|
||||
protocols []string
|
||||
}
|
||||
|
||||
// NewSPDYExecutor connects to the provided server and upgrades the connection to
|
||||
// multiplexed bidirectional streams.
|
||||
func NewSPDYExecutor(config *restclient.Config, method string, url *url.URL) (Executor, error) {
|
||||
wrapper, upgradeRoundTripper, err := spdy.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSPDYExecutorForTransports(wrapper, upgradeRoundTripper, method, url)
|
||||
}
|
||||
|
||||
// NewSPDYExecutorForTransports connects to the provided server using the given transport,
|
||||
// upgrades the response using the given upgrader to multiplexed bidirectional streams.
|
||||
func NewSPDYExecutorForTransports(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL) (Executor, error) {
|
||||
return NewSPDYExecutorForProtocols(
|
||||
transport, upgrader, method, url,
|
||||
remotecommand.StreamProtocolV4Name,
|
||||
remotecommand.StreamProtocolV3Name,
|
||||
remotecommand.StreamProtocolV2Name,
|
||||
remotecommand.StreamProtocolV1Name,
|
||||
)
|
||||
}
|
||||
|
||||
// NewSPDYExecutorForProtocols connects to the provided server and upgrades the connection to
|
||||
// multiplexed bidirectional streams using only the provided protocols. Exposed for testing, most
|
||||
// callers should use NewSPDYExecutor or NewSPDYExecutorForTransports.
|
||||
func NewSPDYExecutorForProtocols(transport http.RoundTripper, upgrader spdy.Upgrader, method string, url *url.URL, protocols ...string) (Executor, error) {
|
||||
return &streamExecutor{
|
||||
upgrader: upgrader,
|
||||
transport: transport,
|
||||
method: method,
|
||||
url: url,
|
||||
protocols: protocols,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stream opens a protocol streamer to the server and streams until a client closes
|
||||
// the connection or the server disconnects.
|
||||
func (e *streamExecutor) Stream(options StreamOptions) error {
|
||||
req, err := http.NewRequest(e.method, e.url.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %v", err)
|
||||
}
|
||||
|
||||
conn, protocol, err := spdy.Negotiate(
|
||||
e.upgrader,
|
||||
&http.Client{Transport: e.transport},
|
||||
req,
|
||||
e.protocols...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var streamer streamProtocolHandler
|
||||
|
||||
switch protocol {
|
||||
case remotecommand.StreamProtocolV4Name:
|
||||
streamer = newStreamProtocolV4(options)
|
||||
case remotecommand.StreamProtocolV3Name:
|
||||
streamer = newStreamProtocolV3(options)
|
||||
case remotecommand.StreamProtocolV2Name:
|
||||
streamer = newStreamProtocolV2(options)
|
||||
case "":
|
||||
glog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommand.StreamProtocolV1Name:
|
||||
streamer = newStreamProtocolV1(options)
|
||||
}
|
||||
|
||||
return streamer.stream(conn)
|
||||
}
|
||||
33
vendor/k8s.io/client-go/tools/remotecommand/resize.go
generated
vendored
Normal file
33
vendor/k8s.io/client-go/tools/remotecommand/resize.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
// TerminalSize and TerminalSizeQueue was a part of k8s.io/kubernetes/pkg/util/term
|
||||
// and were moved in order to decouple client from other term dependencies
|
||||
|
||||
// TerminalSize represents the width and height of a terminal.
|
||||
type TerminalSize struct {
|
||||
Width uint16
|
||||
Height uint16
|
||||
}
|
||||
|
||||
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
|
||||
type TerminalSizeQueue interface {
|
||||
// Next returns the new terminal size after the terminal has been resized. It returns nil when
|
||||
// monitoring has been stopped.
|
||||
Next() *TerminalSize
|
||||
}
|
||||
160
vendor/k8s.io/client-go/tools/remotecommand/v1.go
generated
vendored
Normal file
160
vendor/k8s.io/client-go/tools/remotecommand/v1.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
)
|
||||
|
||||
// streamProtocolV1 implements the first version of the streaming exec & attach
|
||||
// protocol. This version has some bugs, such as not being able to detect when
|
||||
// non-interactive stdin data has ended. See http://issues.k8s.io/13394 and
|
||||
// http://issues.k8s.io/13395 for more details.
|
||||
type streamProtocolV1 struct {
|
||||
StreamOptions
|
||||
|
||||
errorStream httpstream.Stream
|
||||
remoteStdin httpstream.Stream
|
||||
remoteStdout httpstream.Stream
|
||||
remoteStderr httpstream.Stream
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV1{}
|
||||
|
||||
func newStreamProtocolV1(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV1{
|
||||
StreamOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV1) stream(conn streamCreator) error {
|
||||
doneChan := make(chan struct{}, 2)
|
||||
errorChan := make(chan error)
|
||||
|
||||
cp := func(s string, dst io.Writer, src io.Reader) {
|
||||
glog.V(6).Infof("Copying %s", s)
|
||||
defer glog.V(6).Infof("Done copying %s", s)
|
||||
if _, err := io.Copy(dst, src); err != nil && err != io.EOF {
|
||||
glog.Errorf("Error copying %s: %v", s, err)
|
||||
}
|
||||
if s == v1.StreamTypeStdout || s == v1.StreamTypeStderr {
|
||||
doneChan <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// set up all the streams first
|
||||
var err error
|
||||
headers := http.Header{}
|
||||
headers.Set(v1.StreamType, v1.StreamTypeError)
|
||||
p.errorStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.errorStream.Reset()
|
||||
|
||||
// Create all the streams first, then start the copy goroutines. The server doesn't start its copy
|
||||
// goroutines until it's received all of the streams. If the client creates the stdin stream and
|
||||
// immediately begins copying stdin data to the server, it's possible to overwhelm and wedge the
|
||||
// spdy frame handler in the server so that it is full of unprocessed frames. The frames aren't
|
||||
// getting processed because the server hasn't started its copying, and it won't do that until it
|
||||
// gets all the streams. By creating all the streams first, we ensure that the server is ready to
|
||||
// process data before the client starts sending any. See https://issues.k8s.io/16373 for more info.
|
||||
if p.Stdin != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdin)
|
||||
p.remoteStdin, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStdin.Reset()
|
||||
}
|
||||
|
||||
if p.Stdout != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdout)
|
||||
p.remoteStdout, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStdout.Reset()
|
||||
}
|
||||
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStderr)
|
||||
p.remoteStderr, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.remoteStderr.Reset()
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
// always read from errorStream
|
||||
go func() {
|
||||
message, err := ioutil.ReadAll(p.errorStream)
|
||||
if err != nil && err != io.EOF {
|
||||
errorChan <- fmt.Errorf("Error reading from error stream: %s", err)
|
||||
return
|
||||
}
|
||||
if len(message) > 0 {
|
||||
errorChan <- fmt.Errorf("Error executing remote command: %s", message)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if p.Stdin != nil {
|
||||
// TODO this goroutine will never exit cleanly (the io.Copy never unblocks)
|
||||
// because stdin is not closed until the process exits. If we try to call
|
||||
// stdin.Close(), it returns no error but doesn't unblock the copy. It will
|
||||
// exit when the process exits, instead.
|
||||
go cp(v1.StreamTypeStdin, p.remoteStdin, p.Stdin)
|
||||
}
|
||||
|
||||
waitCount := 0
|
||||
completedStreams := 0
|
||||
|
||||
if p.Stdout != nil {
|
||||
waitCount++
|
||||
go cp(v1.StreamTypeStdout, p.Stdout, p.remoteStdout)
|
||||
}
|
||||
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
waitCount++
|
||||
go cp(v1.StreamTypeStderr, p.Stderr, p.remoteStderr)
|
||||
}
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case <-doneChan:
|
||||
completedStreams++
|
||||
if completedStreams == waitCount {
|
||||
break Loop
|
||||
}
|
||||
case err := <-errorChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
195
vendor/k8s.io/client-go/tools/remotecommand/v2.go
generated
vendored
Normal file
195
vendor/k8s.io/client-go/tools/remotecommand/v2.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// streamProtocolV2 implements version 2 of the streaming protocol for attach
|
||||
// and exec. The original streaming protocol was metav1. As a result, this
|
||||
// version is referred to as version 2, even though it is the first actual
|
||||
// numbered version.
|
||||
type streamProtocolV2 struct {
|
||||
StreamOptions
|
||||
|
||||
errorStream io.Reader
|
||||
remoteStdin io.ReadWriteCloser
|
||||
remoteStdout io.Reader
|
||||
remoteStderr io.Reader
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV2{}
|
||||
|
||||
func newStreamProtocolV2(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV2{
|
||||
StreamOptions: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) createStreams(conn streamCreator) error {
|
||||
var err error
|
||||
headers := http.Header{}
|
||||
|
||||
// set up error stream
|
||||
headers.Set(v1.StreamType, v1.StreamTypeError)
|
||||
p.errorStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up stdin stream
|
||||
if p.Stdin != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdin)
|
||||
p.remoteStdin, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set up stdout stream
|
||||
if p.Stdout != nil {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStdout)
|
||||
p.remoteStdout, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set up stderr stream
|
||||
if p.Stderr != nil && !p.Tty {
|
||||
headers.Set(v1.StreamType, v1.StreamTypeStderr)
|
||||
p.remoteStderr, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStdin() {
|
||||
if p.Stdin != nil {
|
||||
var once sync.Once
|
||||
|
||||
// copy from client's stdin to container's stdin
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
// if p.stdin is noninteractive, p.g. `echo abc | kubectl exec -i <pod> -- cat`, make sure
|
||||
// we close remoteStdin as soon as the copy from p.stdin to remoteStdin finishes. Otherwise
|
||||
// the executed command will remain running.
|
||||
defer once.Do(func() { p.remoteStdin.Close() })
|
||||
|
||||
if _, err := io.Copy(p.remoteStdin, p.Stdin); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// read from remoteStdin until the stream is closed. this is essential to
|
||||
// be able to exit interactive sessions cleanly and not leak goroutines or
|
||||
// hang the client's terminal.
|
||||
//
|
||||
// TODO we aren't using go-dockerclient any more; revisit this to determine if it's still
|
||||
// required by engine-api.
|
||||
//
|
||||
// go-dockerclient's current hijack implementation
|
||||
// (https://github.com/fsouza/go-dockerclient/blob/89f3d56d93788dfe85f864a44f85d9738fca0670/client.go#L564)
|
||||
// waits for all three streams (stdin/stdout/stderr) to finish copying
|
||||
// before returning. When hijack finishes copying stdout/stderr, it calls
|
||||
// Close() on its side of remoteStdin, which allows this copy to complete.
|
||||
// When that happens, we must Close() on our side of remoteStdin, to
|
||||
// allow the copy in hijack to complete, and hijack to return.
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer once.Do(func() { p.remoteStdin.Close() })
|
||||
|
||||
// this "copy" doesn't actually read anything - it's just here to wait for
|
||||
// the server to close remoteStdin.
|
||||
if _, err := io.Copy(ioutil.Discard, p.remoteStdin); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStdout(wg *sync.WaitGroup) {
|
||||
if p.Stdout == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer wg.Done()
|
||||
|
||||
if _, err := io.Copy(p.Stdout, p.remoteStdout); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) copyStderr(wg *sync.WaitGroup) {
|
||||
if p.Stderr == nil || p.Tty {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
defer wg.Done()
|
||||
|
||||
if _, err := io.Copy(p.Stderr, p.remoteStderr); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV2) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV2{})
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// errorDecoderV2 interprets the error channel data as plain text.
|
||||
type errorDecoderV2 struct{}
|
||||
|
||||
func (d *errorDecoderV2) decode(message []byte) error {
|
||||
return fmt.Errorf("error executing remote command: %s", message)
|
||||
}
|
||||
111
vendor/k8s.io/client-go/tools/remotecommand/v3.go
generated
vendored
Normal file
111
vendor/k8s.io/client-go/tools/remotecommand/v3.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// streamProtocolV3 implements version 3 of the streaming protocol for attach
|
||||
// and exec. This version adds support for resizing the container's terminal.
|
||||
type streamProtocolV3 struct {
|
||||
*streamProtocolV2
|
||||
|
||||
resizeStream io.Writer
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV3{}
|
||||
|
||||
func newStreamProtocolV3(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV3{
|
||||
streamProtocolV2: newStreamProtocolV2(options).(*streamProtocolV2),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) createStreams(conn streamCreator) error {
|
||||
// set up the streams from v2
|
||||
if err := p.streamProtocolV2.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set up resize stream
|
||||
if p.Tty {
|
||||
headers := http.Header{}
|
||||
headers.Set(v1.StreamType, v1.StreamTypeResize)
|
||||
var err error
|
||||
p.resizeStream, err = conn.CreateStream(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) handleResizes() {
|
||||
if p.resizeStream == nil || p.TerminalSizeQueue == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
encoder := json.NewEncoder(p.resizeStream)
|
||||
for {
|
||||
size := p.TerminalSizeQueue.Next()
|
||||
if size == nil {
|
||||
return
|
||||
}
|
||||
if err := encoder.Encode(&size); err != nil {
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV3) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV3{})
|
||||
|
||||
p.handleResizes()
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
type errorDecoderV3 struct {
|
||||
errorDecoderV2
|
||||
}
|
||||
119
vendor/k8s.io/client-go/tools/remotecommand/v4.go
generated
vendored
Normal file
119
vendor/k8s.io/client-go/tools/remotecommand/v4.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/client-go/util/exec"
|
||||
)
|
||||
|
||||
// streamProtocolV4 implements version 4 of the streaming protocol for attach
|
||||
// and exec. This version adds support for exit codes on the error stream through
|
||||
// the use of metav1.Status instead of plain text messages.
|
||||
type streamProtocolV4 struct {
|
||||
*streamProtocolV3
|
||||
}
|
||||
|
||||
var _ streamProtocolHandler = &streamProtocolV4{}
|
||||
|
||||
func newStreamProtocolV4(options StreamOptions) streamProtocolHandler {
|
||||
return &streamProtocolV4{
|
||||
streamProtocolV3: newStreamProtocolV3(options).(*streamProtocolV3),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) createStreams(conn streamCreator) error {
|
||||
return p.streamProtocolV3.createStreams(conn)
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) handleResizes() {
|
||||
p.streamProtocolV3.handleResizes()
|
||||
}
|
||||
|
||||
func (p *streamProtocolV4) stream(conn streamCreator) error {
|
||||
if err := p.createStreams(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now that all the streams have been created, proceed with reading & copying
|
||||
|
||||
errorChan := watchErrorStream(p.errorStream, &errorDecoderV4{})
|
||||
|
||||
p.handleResizes()
|
||||
|
||||
p.copyStdin()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
p.copyStdout(&wg)
|
||||
p.copyStderr(&wg)
|
||||
|
||||
// we're waiting for stdout/stderr to finish copying
|
||||
wg.Wait()
|
||||
|
||||
// waits for errorStream to finish reading with an error or nil
|
||||
return <-errorChan
|
||||
}
|
||||
|
||||
// errorDecoderV4 interprets the json-marshaled metav1.Status on the error channel
|
||||
// and creates an exec.ExitError from it.
|
||||
type errorDecoderV4 struct{}
|
||||
|
||||
func (d *errorDecoderV4) decode(message []byte) error {
|
||||
status := metav1.Status{}
|
||||
err := json.Unmarshal(message, &status)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stream protocol error: %v in %q", err, string(message))
|
||||
}
|
||||
switch status.Status {
|
||||
case metav1.StatusSuccess:
|
||||
return nil
|
||||
case metav1.StatusFailure:
|
||||
if status.Reason == remotecommand.NonZeroExitCodeReason {
|
||||
if status.Details == nil {
|
||||
return errors.New("error stream protocol error: details must be set")
|
||||
}
|
||||
for i := range status.Details.Causes {
|
||||
c := &status.Details.Causes[i]
|
||||
if c.Type != remotecommand.ExitCodeCauseType {
|
||||
continue
|
||||
}
|
||||
|
||||
rc, err := strconv.ParseUint(c.Message, 10, 8)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error stream protocol error: invalid exit code value %q", c.Message)
|
||||
}
|
||||
return exec.CodeExitError{
|
||||
Err: fmt.Errorf("command terminated with exit code %d", rc),
|
||||
Code: int(rc),
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("error stream protocol error: no %s cause given", remotecommand.ExitCodeCauseType)
|
||||
}
|
||||
default:
|
||||
return errors.New("error stream protocol error: unknown error")
|
||||
}
|
||||
|
||||
return fmt.Errorf(status.Message)
|
||||
}
|
||||
94
vendor/k8s.io/client-go/transport/spdy/spdy.go
generated
vendored
Normal file
94
vendor/k8s.io/client-go/transport/spdy/spdy.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// Upgrader validates a response from the server after a SPDY upgrade.
|
||||
type Upgrader interface {
|
||||
// NewConnection validates the response and creates a new Connection.
|
||||
NewConnection(resp *http.Response) (httpstream.Connection, error)
|
||||
}
|
||||
|
||||
// RoundTripperFor returns a round tripper and upgrader to use with SPDY.
|
||||
func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, error) {
|
||||
tlsConfig, err := restclient.TLSConfigFor(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true)
|
||||
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return wrapper, upgradeRoundTripper, nil
|
||||
}
|
||||
|
||||
// dialer implements the httpstream.Dialer interface.
|
||||
type dialer struct {
|
||||
client *http.Client
|
||||
upgrader Upgrader
|
||||
method string
|
||||
url *url.URL
|
||||
}
|
||||
|
||||
var _ httpstream.Dialer = &dialer{}
|
||||
|
||||
// NewDialer will create a dialer that connects to the provided URL and upgrades the connection to SPDY.
|
||||
func NewDialer(upgrader Upgrader, client *http.Client, method string, url *url.URL) httpstream.Dialer {
|
||||
return &dialer{
|
||||
client: client,
|
||||
upgrader: upgrader,
|
||||
method: method,
|
||||
url: url,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
req, err := http.NewRequest(d.method, d.url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error creating request: %v", err)
|
||||
}
|
||||
return Negotiate(d.upgrader, d.client, req, protocols...)
|
||||
}
|
||||
|
||||
// Negotiate opens a connection to a remote server and attempts to negotiate
|
||||
// a SPDY connection. Upon success, it returns the connection and the protocol selected by
|
||||
// the server. The client transport must use the upgradeRoundTripper - see RoundTripperFor.
|
||||
func Negotiate(upgrader Upgrader, client *http.Client, req *http.Request, protocols ...string) (httpstream.Connection, string, error) {
|
||||
for i := range protocols {
|
||||
req.Header.Add(httpstream.HeaderProtocolVersion, protocols[i])
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error sending request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
conn, err := upgrader.NewConnection(resp)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil
|
||||
}
|
||||
52
vendor/k8s.io/client-go/util/exec/exec.go
generated
vendored
Normal file
52
vendor/k8s.io/client-go/util/exec/exec.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package exec
|
||||
|
||||
// ExitError is an interface that presents an API similar to os.ProcessState, which is
|
||||
// what ExitError from os/exec is. This is designed to make testing a bit easier and
|
||||
// probably loses some of the cross-platform properties of the underlying library.
|
||||
type ExitError interface {
|
||||
String() string
|
||||
Error() string
|
||||
Exited() bool
|
||||
ExitStatus() int
|
||||
}
|
||||
|
||||
// CodeExitError is an implementation of ExitError consisting of an error object
|
||||
// and an exit code (the upper bits of os.exec.ExitStatus).
|
||||
type CodeExitError struct {
|
||||
Err error
|
||||
Code int
|
||||
}
|
||||
|
||||
var _ ExitError = CodeExitError{}
|
||||
|
||||
func (e CodeExitError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e CodeExitError) String() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e CodeExitError) Exited() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e CodeExitError) ExitStatus() int {
|
||||
return e.Code
|
||||
}
|
||||
92
vendor/k8s.io/kubernetes/pkg/apis/core/annotation_key_constants.go
generated
vendored
Normal file
92
vendor/k8s.io/kubernetes/pkg/apis/core/annotation_key_constants.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file should be consistent with pkg/api/v1/annotation_key_constants.go.
|
||||
|
||||
package core
|
||||
|
||||
const (
|
||||
// ImagePolicyFailedOpenKey is added to pods created by failing open when the image policy
|
||||
// webhook backend fails.
|
||||
ImagePolicyFailedOpenKey string = "alpha.image-policy.k8s.io/failed-open"
|
||||
|
||||
// PodPresetOptOutAnnotationKey represents the annotation key for a pod to exempt itself from pod preset manipulation
|
||||
PodPresetOptOutAnnotationKey string = "podpreset.admission.kubernetes.io/exclude"
|
||||
|
||||
// MirrorAnnotationKey represents the annotation key set by kubelets when creating mirror pods
|
||||
MirrorPodAnnotationKey string = "kubernetes.io/config.mirror"
|
||||
|
||||
// TolerationsAnnotationKey represents the key of tolerations data (json serialized)
|
||||
// in the Annotations of a Pod.
|
||||
TolerationsAnnotationKey string = "scheduler.alpha.kubernetes.io/tolerations"
|
||||
|
||||
// TaintsAnnotationKey represents the key of taints data (json serialized)
|
||||
// in the Annotations of a Node.
|
||||
TaintsAnnotationKey string = "scheduler.alpha.kubernetes.io/taints"
|
||||
|
||||
// SeccompPodAnnotationKey represents the key of a seccomp profile applied
|
||||
// to all containers of a pod.
|
||||
SeccompPodAnnotationKey string = "seccomp.security.alpha.kubernetes.io/pod"
|
||||
|
||||
// SeccompContainerAnnotationKeyPrefix represents the key of a seccomp profile applied
|
||||
// to one container of a pod.
|
||||
SeccompContainerAnnotationKeyPrefix string = "container.seccomp.security.alpha.kubernetes.io/"
|
||||
|
||||
// PreferAvoidPodsAnnotationKey represents the key of preferAvoidPods data (json serialized)
|
||||
// in the Annotations of a Node.
|
||||
PreferAvoidPodsAnnotationKey string = "scheduler.alpha.kubernetes.io/preferAvoidPods"
|
||||
|
||||
// SysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
|
||||
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
|
||||
// key-value pairs. Only a limited set of whitelisted and isolated sysctls is supported by
|
||||
// the kubelet. Pods with other sysctls will fail to launch.
|
||||
SysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/sysctls"
|
||||
|
||||
// UnsafeSysctlsPodAnnotationKey represents the key of sysctls which are set for the infrastructure
|
||||
// container of a pod. The annotation value is a comma separated list of sysctl_name=value
|
||||
// key-value pairs. Unsafe sysctls must be explicitly enabled for a kubelet. They are properly
|
||||
// namespaced to a pod or a container, but their isolation is usually unclear or weak. Their use
|
||||
// is at-your-own-risk. Pods that attempt to set an unsafe sysctl that is not enabled for a kubelet
|
||||
// will fail to launch.
|
||||
UnsafeSysctlsPodAnnotationKey string = "security.alpha.kubernetes.io/unsafe-sysctls"
|
||||
|
||||
// ObjectTTLAnnotations represents a suggestion for kubelet for how long it can cache
|
||||
// an object (e.g. secret, config map) before fetching it again from apiserver.
|
||||
// This annotation can be attached to node.
|
||||
ObjectTTLAnnotationKey string = "node.alpha.kubernetes.io/ttl"
|
||||
|
||||
// BootstrapCheckpointAnnotationKey represents a Resource (Pod) that should be checkpointed by
|
||||
// the kubelet prior to running
|
||||
BootstrapCheckpointAnnotationKey string = "node.kubernetes.io/bootstrap-checkpoint"
|
||||
|
||||
// annotation key prefix used to identify non-convertible json paths.
|
||||
NonConvertibleAnnotationPrefix = "non-convertible.kubernetes.io"
|
||||
|
||||
kubectlPrefix = "kubectl.kubernetes.io/"
|
||||
|
||||
// LastAppliedConfigAnnotation is the annotation used to store the previous
|
||||
// configuration of a resource for use in a three way diff by UpdateApplyAnnotation.
|
||||
LastAppliedConfigAnnotation = kubectlPrefix + "last-applied-configuration"
|
||||
|
||||
// AnnotationLoadBalancerSourceRangesKey is the key of the annotation on a service to set allowed ingress ranges on their LoadBalancers
|
||||
//
|
||||
// It should be a comma-separated list of CIDRs, e.g. `0.0.0.0/0` to
|
||||
// allow full access (the default) or `18.0.0.0/8,56.0.0.0/8` to allow
|
||||
// access only from the CIDRs currently allocated to MIT & the USPS.
|
||||
//
|
||||
// Not all cloud providers support this annotation, though AWS & GCE do.
|
||||
AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges"
|
||||
)
|
||||
24
vendor/k8s.io/kubernetes/pkg/apis/core/doc.go
generated
vendored
Normal file
24
vendor/k8s.io/kubernetes/pkg/apis/core/doc.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
// Package api contains the latest (or "internal") version of the
|
||||
// Kubernetes API objects. This is the API objects as represented in memory.
|
||||
// The contract presented to clients is located in the versioned packages,
|
||||
// which are sub-directories. The first one is "v1". Those packages
|
||||
// describe how a particular version is serialized to storage/network.
|
||||
package core // import "k8s.io/kubernetes/pkg/apis/core"
|
||||
38
vendor/k8s.io/kubernetes/pkg/apis/core/field_constants.go
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/pkg/apis/core/field_constants.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
// Field path constants that are specific to the internal API
|
||||
// representation.
|
||||
const (
|
||||
NodeUnschedulableField = "spec.unschedulable"
|
||||
ObjectNameField = "metadata.name"
|
||||
PodHostField = "spec.nodeName"
|
||||
PodStatusField = "status.phase"
|
||||
SecretTypeField = "type"
|
||||
|
||||
EventReasonField = "action"
|
||||
EventSourceField = "reportingComponent"
|
||||
EventTypeField = "type"
|
||||
EventInvolvedKindField = "involvedObject.kind"
|
||||
EventInvolvedNamespaceField = "involvedObject.namespace"
|
||||
EventInvolvedNameField = "involvedObject.name"
|
||||
EventInvolvedUIDField = "involvedObject.uid"
|
||||
EventInvolvedAPIVersionField = "involvedObject.apiVersion"
|
||||
EventInvolvedResourceVersionField = "involvedObject.resourceVersion"
|
||||
EventInvolvedFieldPathField = "involvedObject.fieldPath"
|
||||
)
|
||||
28
vendor/k8s.io/kubernetes/pkg/apis/core/json.go
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/pkg/apis/core/json.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// This file implements json marshaling/unmarshaling interfaces on objects that are currently marshaled into annotations
|
||||
// to prevent anyone from marshaling these internal structs.
|
||||
|
||||
var _ = json.Marshaler(&AvoidPods{})
|
||||
var _ = json.Unmarshaler(&AvoidPods{})
|
||||
|
||||
func (AvoidPods) MarshalJSON() ([]byte, error) { panic("do not marshal internal struct") }
|
||||
func (*AvoidPods) UnmarshalJSON([]byte) error { panic("do not unmarshal to internal struct") }
|
||||
34
vendor/k8s.io/kubernetes/pkg/apis/core/objectreference.go
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/pkg/apis/core/objectreference.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
//TODO: consider making these methods functions, because we don't want helper
|
||||
//functions in the k8s.io/api repo.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func (obj *ObjectReference) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
||||
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
||||
}
|
||||
|
||||
func (obj *ObjectReference) GroupVersionKind() schema.GroupVersionKind {
|
||||
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
||||
}
|
||||
|
||||
func (obj *ObjectReference) GetObjectKind() schema.ObjectKind { return obj }
|
||||
99
vendor/k8s.io/kubernetes/pkg/apis/core/register.go
generated
vendored
Normal file
99
vendor/k8s.io/kubernetes/pkg/apis/core/register.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = ""
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
if err := scheme.AddIgnoredConversionType(&metav1.TypeMeta{}, &metav1.TypeMeta{}); err != nil {
|
||||
return err
|
||||
}
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Pod{},
|
||||
&PodList{},
|
||||
&PodStatusResult{},
|
||||
&PodTemplate{},
|
||||
&PodTemplateList{},
|
||||
&ReplicationControllerList{},
|
||||
&ReplicationController{},
|
||||
&ServiceList{},
|
||||
&Service{},
|
||||
&ServiceProxyOptions{},
|
||||
&NodeList{},
|
||||
&Node{},
|
||||
&NodeConfigSource{},
|
||||
&NodeProxyOptions{},
|
||||
&Endpoints{},
|
||||
&EndpointsList{},
|
||||
&Binding{},
|
||||
&Event{},
|
||||
&EventList{},
|
||||
&List{},
|
||||
&LimitRange{},
|
||||
&LimitRangeList{},
|
||||
&ResourceQuota{},
|
||||
&ResourceQuotaList{},
|
||||
&Namespace{},
|
||||
&NamespaceList{},
|
||||
&ServiceAccount{},
|
||||
&ServiceAccountList{},
|
||||
&Secret{},
|
||||
&SecretList{},
|
||||
&PersistentVolume{},
|
||||
&PersistentVolumeList{},
|
||||
&PersistentVolumeClaim{},
|
||||
&PersistentVolumeClaimList{},
|
||||
&PodAttachOptions{},
|
||||
&PodLogOptions{},
|
||||
&PodExecOptions{},
|
||||
&PodPortForwardOptions{},
|
||||
&PodProxyOptions{},
|
||||
&ComponentStatus{},
|
||||
&ComponentStatusList{},
|
||||
&SerializedReference{},
|
||||
&RangeAllocation{},
|
||||
&ConfigMap{},
|
||||
&ConfigMapList{},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
62
vendor/k8s.io/kubernetes/pkg/apis/core/resource.go
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/pkg/apis/core/resource.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func (self ResourceName) String() string {
|
||||
return string(self)
|
||||
}
|
||||
|
||||
// Returns the CPU limit if specified.
|
||||
func (self *ResourceList) Cpu() *resource.Quantity {
|
||||
if val, ok := (*self)[ResourceCPU]; ok {
|
||||
return &val
|
||||
}
|
||||
return &resource.Quantity{Format: resource.DecimalSI}
|
||||
}
|
||||
|
||||
// Returns the Memory limit if specified.
|
||||
func (self *ResourceList) Memory() *resource.Quantity {
|
||||
if val, ok := (*self)[ResourceMemory]; ok {
|
||||
return &val
|
||||
}
|
||||
return &resource.Quantity{Format: resource.BinarySI}
|
||||
}
|
||||
|
||||
func (self *ResourceList) Pods() *resource.Quantity {
|
||||
if val, ok := (*self)[ResourcePods]; ok {
|
||||
return &val
|
||||
}
|
||||
return &resource.Quantity{}
|
||||
}
|
||||
|
||||
func (self *ResourceList) NvidiaGPU() *resource.Quantity {
|
||||
if val, ok := (*self)[ResourceNvidiaGPU]; ok {
|
||||
return &val
|
||||
}
|
||||
return &resource.Quantity{}
|
||||
}
|
||||
|
||||
func (self *ResourceList) StorageEphemeral() *resource.Quantity {
|
||||
if val, ok := (*self)[ResourceEphemeralStorage]; ok {
|
||||
return &val
|
||||
}
|
||||
return &resource.Quantity{}
|
||||
}
|
||||
36
vendor/k8s.io/kubernetes/pkg/apis/core/taint.go
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/pkg/apis/core/taint.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
//TODO: consider making these methods functions, because we don't want helper
|
||||
//functions in the k8s.io/api repo.
|
||||
|
||||
package core
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MatchTaint checks if the taint matches taintToMatch. Taints are unique by key:effect,
|
||||
// if the two taints have same key:effect, regard as they match.
|
||||
func (t *Taint) MatchTaint(taintToMatch Taint) bool {
|
||||
return t.Key == taintToMatch.Key && t.Effect == taintToMatch.Effect
|
||||
}
|
||||
|
||||
// taint.ToString() converts taint struct to string in format key=value:effect or key:effect.
|
||||
func (t *Taint) ToString() string {
|
||||
if len(t.Value) == 0 {
|
||||
return fmt.Sprintf("%v:%v", t.Key, t.Effect)
|
||||
}
|
||||
return fmt.Sprintf("%v=%v:%v", t.Key, t.Value, t.Effect)
|
||||
}
|
||||
30
vendor/k8s.io/kubernetes/pkg/apis/core/toleration.go
generated
vendored
Normal file
30
vendor/k8s.io/kubernetes/pkg/apis/core/toleration.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
//TODO: consider making these methods functions, because we don't want helper
|
||||
//functions in the k8s.io/api repo.
|
||||
|
||||
package core
|
||||
|
||||
// MatchToleration checks if the toleration matches tolerationToMatch. Tolerations are unique by <key,effect,operator,value>,
|
||||
// if the two tolerations have same <key,effect,operator,value> combination, regard as they match.
|
||||
// TODO: uniqueness check for tolerations in api validations.
|
||||
func (t *Toleration) MatchToleration(tolerationToMatch *Toleration) bool {
|
||||
return t.Key == tolerationToMatch.Key &&
|
||||
t.Effect == tolerationToMatch.Effect &&
|
||||
t.Operator == tolerationToMatch.Operator &&
|
||||
t.Value == tolerationToMatch.Value
|
||||
}
|
||||
4669
vendor/k8s.io/kubernetes/pkg/apis/core/types.go
generated
vendored
Normal file
4669
vendor/k8s.io/kubernetes/pkg/apis/core/types.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5970
vendor/k8s.io/kubernetes/pkg/apis/core/zz_generated.deepcopy.go
generated
vendored
Normal file
5970
vendor/k8s.io/kubernetes/pkg/apis/core/zz_generated.deepcopy.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
59
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/attach.go
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/attach.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// Attacher knows how to attach to a running container in a pod.
|
||||
type Attacher interface {
|
||||
// AttachContainer attaches to the running container in the pod, copying data between in/out/err
|
||||
// and the container's stdin/stdout/stderr.
|
||||
AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
}
|
||||
|
||||
// ServeAttach handles requests to attach to a container. After creating/receiving the required
|
||||
// streams, it delegates the actual attaching to attacher.
|
||||
func ServeAttach(w http.ResponseWriter, req *http.Request, attacher Attacher, podName string, uid types.UID, container string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
ctx, ok := createStreams(req, w, streamOpts, supportedProtocols, idleTimeout, streamCreationTimeout)
|
||||
if !ok {
|
||||
// error is handled by createStreams
|
||||
return
|
||||
}
|
||||
defer ctx.conn.Close()
|
||||
|
||||
err := attacher.AttachContainer(podName, uid, container, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty, ctx.resizeChan)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error attaching to container: %v", err)
|
||||
runtime.HandleError(err)
|
||||
ctx.writeStatus(apierrors.NewInternalError(err))
|
||||
} else {
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusSuccess,
|
||||
}})
|
||||
}
|
||||
}
|
||||
18
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/doc.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// package remotecommand contains functions related to executing commands in and attaching to pods.
|
||||
package remotecommand // import "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
79
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/exec.go
generated
vendored
Normal file
79
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/exec.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// Executor knows how to execute a command in a container in a pod.
|
||||
type Executor interface {
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
}
|
||||
|
||||
// ServeExec handles requests to execute a command in a container. After
|
||||
// creating/receiving the required streams, it delegates the actual execution
|
||||
// to the executor.
|
||||
func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podName string, uid types.UID, container string, cmd []string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
ctx, ok := createStreams(req, w, streamOpts, supportedProtocols, idleTimeout, streamCreationTimeout)
|
||||
if !ok {
|
||||
// error is handled by createStreams
|
||||
return
|
||||
}
|
||||
defer ctx.conn.Close()
|
||||
|
||||
err := executor.ExecInContainer(podName, uid, container, cmd, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty, ctx.resizeChan, 0)
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(utilexec.ExitError); ok && exitErr.Exited() {
|
||||
rc := exitErr.ExitStatus()
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: remotecommandconsts.NonZeroExitCodeReason,
|
||||
Details: &metav1.StatusDetails{
|
||||
Causes: []metav1.StatusCause{
|
||||
{
|
||||
Type: remotecommandconsts.ExitCodeCauseType,
|
||||
Message: fmt.Sprintf("%d", rc),
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: fmt.Sprintf("command terminated with non-zero exit code: %v", exitErr),
|
||||
}})
|
||||
} else {
|
||||
err = fmt.Errorf("error executing command in container: %v", err)
|
||||
runtime.HandleError(err)
|
||||
ctx.writeStatus(apierrors.NewInternalError(err))
|
||||
}
|
||||
} else {
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusSuccess,
|
||||
}})
|
||||
}
|
||||
}
|
||||
447
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/httpstream.go
generated
vendored
Normal file
447
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/httpstream.go
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Options contains details about which streams are required for
|
||||
// remote command execution.
|
||||
type Options struct {
|
||||
Stdin bool
|
||||
Stdout bool
|
||||
Stderr bool
|
||||
TTY bool
|
||||
}
|
||||
|
||||
// NewOptions creates a new Options from the Request.
|
||||
func NewOptions(req *http.Request) (*Options, error) {
|
||||
tty := req.FormValue(api.ExecTTYParam) == "1"
|
||||
stdin := req.FormValue(api.ExecStdinParam) == "1"
|
||||
stdout := req.FormValue(api.ExecStdoutParam) == "1"
|
||||
stderr := req.FormValue(api.ExecStderrParam) == "1"
|
||||
if tty && stderr {
|
||||
// TODO: make this an error before we reach this method
|
||||
glog.V(4).Infof("Access to exec with tty and stderr is not supported, bypassing stderr")
|
||||
stderr = false
|
||||
}
|
||||
|
||||
if !stdin && !stdout && !stderr {
|
||||
return nil, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")
|
||||
}
|
||||
|
||||
return &Options{
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
TTY: tty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// context contains the connection and streams used when
|
||||
// forwarding an attach or execute session into a container.
|
||||
type context struct {
|
||||
conn io.Closer
|
||||
stdinStream io.ReadCloser
|
||||
stdoutStream io.WriteCloser
|
||||
stderrStream io.WriteCloser
|
||||
writeStatus func(status *apierrors.StatusError) error
|
||||
resizeStream io.ReadCloser
|
||||
resizeChan chan remotecommand.TerminalSize
|
||||
tty bool
|
||||
}
|
||||
|
||||
// streamAndReply holds both a Stream and a channel that is closed when the stream's reply frame is
|
||||
// enqueued. Consumers can wait for replySent to be closed prior to proceeding, to ensure that the
|
||||
// replyFrame is enqueued before the connection's goaway frame is sent (e.g. if a stream was
|
||||
// received and right after, the connection gets closed).
|
||||
type streamAndReply struct {
|
||||
httpstream.Stream
|
||||
replySent <-chan struct{}
|
||||
}
|
||||
|
||||
// waitStreamReply waits until either replySent or stop is closed. If replySent is closed, it sends
|
||||
// an empty struct to the notify channel.
|
||||
func waitStreamReply(replySent <-chan struct{}, notify chan<- struct{}, stop <-chan struct{}) {
|
||||
select {
|
||||
case <-replySent:
|
||||
notify <- struct{}{}
|
||||
case <-stop:
|
||||
}
|
||||
}
|
||||
|
||||
func createStreams(req *http.Request, w http.ResponseWriter, opts *Options, supportedStreamProtocols []string, idleTimeout, streamCreationTimeout time.Duration) (*context, bool) {
|
||||
var ctx *context
|
||||
var ok bool
|
||||
if wsstream.IsWebSocketRequest(req) {
|
||||
ctx, ok = createWebSocketStreams(req, w, opts, idleTimeout)
|
||||
} else {
|
||||
ctx, ok = createHttpStreamStreams(req, w, opts, supportedStreamProtocols, idleTimeout, streamCreationTimeout)
|
||||
}
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if ctx.resizeStream != nil {
|
||||
ctx.resizeChan = make(chan remotecommand.TerminalSize)
|
||||
go handleResizeEvents(ctx.resizeStream, ctx.resizeChan)
|
||||
}
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
|
||||
func createHttpStreamStreams(req *http.Request, w http.ResponseWriter, opts *Options, supportedStreamProtocols []string, idleTimeout, streamCreationTimeout time.Duration) (*context, bool) {
|
||||
protocol, err := httpstream.Handshake(req, w, supportedStreamProtocols)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, err.Error())
|
||||
return nil, false
|
||||
}
|
||||
|
||||
streamCh := make(chan streamAndReply)
|
||||
|
||||
upgrader := spdy.NewResponseUpgrader()
|
||||
conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error {
|
||||
streamCh <- streamAndReply{Stream: stream, replySent: replySent}
|
||||
return nil
|
||||
})
|
||||
// from this point on, we can no longer call methods on response
|
||||
if conn == nil {
|
||||
// The upgrader is responsible for notifying the client of any errors that
|
||||
// occurred during upgrading. All we can do is return here at this point
|
||||
// if we weren't successful in upgrading.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
conn.SetIdleTimeout(idleTimeout)
|
||||
|
||||
var handler protocolHandler
|
||||
switch protocol {
|
||||
case remotecommandconsts.StreamProtocolV4Name:
|
||||
handler = &v4ProtocolHandler{}
|
||||
case remotecommandconsts.StreamProtocolV3Name:
|
||||
handler = &v3ProtocolHandler{}
|
||||
case remotecommandconsts.StreamProtocolV2Name:
|
||||
handler = &v2ProtocolHandler{}
|
||||
case "":
|
||||
glog.V(4).Infof("Client did not request protocol negotiation. Falling back to %q", remotecommandconsts.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommandconsts.StreamProtocolV1Name:
|
||||
handler = &v1ProtocolHandler{}
|
||||
}
|
||||
|
||||
// count the streams client asked for, starting with 1
|
||||
expectedStreams := 1
|
||||
if opts.Stdin {
|
||||
expectedStreams++
|
||||
}
|
||||
if opts.Stdout {
|
||||
expectedStreams++
|
||||
}
|
||||
if opts.Stderr {
|
||||
expectedStreams++
|
||||
}
|
||||
if opts.TTY && handler.supportsTerminalResizing() {
|
||||
expectedStreams++
|
||||
}
|
||||
|
||||
expired := time.NewTimer(streamCreationTimeout)
|
||||
defer expired.Stop()
|
||||
|
||||
ctx, err := handler.waitForStreams(streamCh, expectedStreams, expired.C)
|
||||
if err != nil {
|
||||
runtime.HandleError(err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ctx.conn = conn
|
||||
ctx.tty = opts.TTY
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
|
||||
type protocolHandler interface {
|
||||
// waitForStreams waits for the expected streams or a timeout, returning a
|
||||
// remoteCommandContext if all the streams were received, or an error if not.
|
||||
waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error)
|
||||
// supportsTerminalResizing returns true if the protocol handler supports terminal resizing
|
||||
supportsTerminalResizing() bool
|
||||
}
|
||||
|
||||
// v4ProtocolHandler implements the V4 protocol version for streaming command execution. It only differs
|
||||
// in from v3 in the error stream format using an json-marshaled metav1.Status which carries
|
||||
// the process' exit code.
|
||||
type v4ProtocolHandler struct{}
|
||||
|
||||
func (*v4ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v4WriteStatusFunc(stream) // write json errors
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeResize:
|
||||
ctx.resizeStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("Unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns true because v4ProtocolHandler supports it
|
||||
func (*v4ProtocolHandler) supportsTerminalResizing() bool { return true }
|
||||
|
||||
// v3ProtocolHandler implements the V3 protocol version for streaming command execution.
|
||||
type v3ProtocolHandler struct{}
|
||||
|
||||
func (*v3ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeResize:
|
||||
ctx.resizeStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("Unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns true because v3ProtocolHandler supports it
|
||||
func (*v3ProtocolHandler) supportsTerminalResizing() bool { return true }
|
||||
|
||||
// v2ProtocolHandler implements the V2 protocol version for streaming command execution.
|
||||
type v2ProtocolHandler struct{}
|
||||
|
||||
func (*v2ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("Unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns false because v2ProtocolHandler doesn't support it.
|
||||
func (*v2ProtocolHandler) supportsTerminalResizing() bool { return false }
|
||||
|
||||
// v1ProtocolHandler implements the V1 protocol version for streaming command execution.
|
||||
type v1ProtocolHandler struct{}
|
||||
|
||||
func (*v1ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) {
|
||||
ctx := &context{}
|
||||
receivedStreams := 0
|
||||
replyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
WaitForStreams:
|
||||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
|
||||
// This defer statement shouldn't be here, but due to previous refactoring, it ended up in
|
||||
// here. This is what 1.0.x kubelets do, so we're retaining that behavior. This is fixed in
|
||||
// the v2ProtocolHandler.
|
||||
defer stream.Reset()
|
||||
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
runtime.HandleError(fmt.Errorf("Unexpected stream type: %q", streamType))
|
||||
}
|
||||
case <-replyChan:
|
||||
receivedStreams++
|
||||
if receivedStreams == expectedStreams {
|
||||
break WaitForStreams
|
||||
}
|
||||
case <-expired:
|
||||
// TODO find a way to return the error to the user. Maybe use a separate
|
||||
// stream to report errors?
|
||||
return nil, errors.New("timed out waiting for client to create streams")
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.stdinStream != nil {
|
||||
ctx.stdinStream.Close()
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// supportsTerminalResizing returns false because v1ProtocolHandler doesn't support it.
|
||||
func (*v1ProtocolHandler) supportsTerminalResizing() bool { return false }
|
||||
|
||||
func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalSize) {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
decoder := json.NewDecoder(stream)
|
||||
for {
|
||||
size := remotecommand.TerminalSize{}
|
||||
if err := decoder.Decode(&size); err != nil {
|
||||
break
|
||||
}
|
||||
channel <- size
|
||||
}
|
||||
}
|
||||
|
||||
func v1WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
|
||||
return func(status *apierrors.StatusError) error {
|
||||
if status.Status().Status == metav1.StatusSuccess {
|
||||
return nil // send error messages
|
||||
}
|
||||
_, err := stream.Write([]byte(status.Error()))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// v4WriteStatusFunc returns a WriteStatusFunc that marshals a given api Status
|
||||
// as json in the error channel.
|
||||
func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
|
||||
return func(status *apierrors.StatusError) error {
|
||||
bs, err := json.Marshal(status.Status())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = stream.Write(bs)
|
||||
return err
|
||||
}
|
||||
}
|
||||
132
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/websocket.go
generated
vendored
Normal file
132
vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/websocket.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/server/httplog"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
)
|
||||
|
||||
const (
|
||||
stdinChannel = iota
|
||||
stdoutChannel
|
||||
stderrChannel
|
||||
errorChannel
|
||||
resizeChannel
|
||||
|
||||
preV4BinaryWebsocketProtocol = wsstream.ChannelWebSocketProtocol
|
||||
preV4Base64WebsocketProtocol = wsstream.Base64ChannelWebSocketProtocol
|
||||
v4BinaryWebsocketProtocol = "v4." + wsstream.ChannelWebSocketProtocol
|
||||
v4Base64WebsocketProtocol = "v4." + wsstream.Base64ChannelWebSocketProtocol
|
||||
)
|
||||
|
||||
// createChannels returns the standard channel types for a shell connection (STDIN 0, STDOUT 1, STDERR 2)
|
||||
// along with the approximate duplex value. It also creates the error (3) and resize (4) channels.
|
||||
func createChannels(opts *Options) []wsstream.ChannelType {
|
||||
// open the requested channels, and always open the error channel
|
||||
channels := make([]wsstream.ChannelType, 5)
|
||||
channels[stdinChannel] = readChannel(opts.Stdin)
|
||||
channels[stdoutChannel] = writeChannel(opts.Stdout)
|
||||
channels[stderrChannel] = writeChannel(opts.Stderr)
|
||||
channels[errorChannel] = wsstream.WriteChannel
|
||||
channels[resizeChannel] = wsstream.ReadChannel
|
||||
return channels
|
||||
}
|
||||
|
||||
// readChannel returns wsstream.ReadChannel if real is true, or wsstream.IgnoreChannel.
|
||||
func readChannel(real bool) wsstream.ChannelType {
|
||||
if real {
|
||||
return wsstream.ReadChannel
|
||||
}
|
||||
return wsstream.IgnoreChannel
|
||||
}
|
||||
|
||||
// writeChannel returns wsstream.WriteChannel if real is true, or wsstream.IgnoreChannel.
|
||||
func writeChannel(real bool) wsstream.ChannelType {
|
||||
if real {
|
||||
return wsstream.WriteChannel
|
||||
}
|
||||
return wsstream.IgnoreChannel
|
||||
}
|
||||
|
||||
// createWebSocketStreams returns a context containing the websocket connection and
|
||||
// streams needed to perform an exec or an attach.
|
||||
func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *Options, idleTimeout time.Duration) (*context, bool) {
|
||||
channels := createChannels(opts)
|
||||
conn := wsstream.NewConn(map[string]wsstream.ChannelProtocolConfig{
|
||||
"": {
|
||||
Binary: true,
|
||||
Channels: channels,
|
||||
},
|
||||
preV4BinaryWebsocketProtocol: {
|
||||
Binary: true,
|
||||
Channels: channels,
|
||||
},
|
||||
preV4Base64WebsocketProtocol: {
|
||||
Binary: false,
|
||||
Channels: channels,
|
||||
},
|
||||
v4BinaryWebsocketProtocol: {
|
||||
Binary: true,
|
||||
Channels: channels,
|
||||
},
|
||||
v4Base64WebsocketProtocol: {
|
||||
Binary: false,
|
||||
Channels: channels,
|
||||
},
|
||||
})
|
||||
conn.SetIdleTimeout(idleTimeout)
|
||||
negotiatedProtocol, streams, err := conn.Open(httplog.Unlogged(w), req)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("Unable to upgrade websocket connection: %v", err))
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Send an empty message to the lowest writable channel to notify the client the connection is established
|
||||
// TODO: make generic to SPDY and WebSockets and do it outside of this method?
|
||||
switch {
|
||||
case opts.Stdout:
|
||||
streams[stdoutChannel].Write([]byte{})
|
||||
case opts.Stderr:
|
||||
streams[stderrChannel].Write([]byte{})
|
||||
default:
|
||||
streams[errorChannel].Write([]byte{})
|
||||
}
|
||||
|
||||
ctx := &context{
|
||||
conn: conn,
|
||||
stdinStream: streams[stdinChannel],
|
||||
stdoutStream: streams[stdoutChannel],
|
||||
stderrStream: streams[stderrChannel],
|
||||
tty: opts.TTY,
|
||||
resizeStream: streams[resizeChannel],
|
||||
}
|
||||
|
||||
switch negotiatedProtocol {
|
||||
case v4BinaryWebsocketProtocol, v4Base64WebsocketProtocol:
|
||||
ctx.writeStatus = v4WriteStatusFunc(streams[errorChannel])
|
||||
default:
|
||||
ctx.writeStatus = v1WriteStatusFunc(streams[errorChannel])
|
||||
}
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
202
vendor/k8s.io/utils/LICENSE
generated
vendored
Normal file
202
vendor/k8s.io/utils/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
18
vendor/k8s.io/utils/exec/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/utils/exec/doc.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package exec provides an injectable interface and implementations for running commands.
|
||||
package exec // import "k8s.io/utils/exec"
|
||||
215
vendor/k8s.io/utils/exec/exec.go
generated
vendored
Normal file
215
vendor/k8s.io/utils/exec/exec.go
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
osexec "os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrExecutableNotFound is returned if the executable is not found.
|
||||
var ErrExecutableNotFound = osexec.ErrNotFound
|
||||
|
||||
// Interface is an interface that presents a subset of the os/exec API. Use this
|
||||
// when you want to inject fakeable/mockable exec behavior.
|
||||
type Interface interface {
|
||||
// Command returns a Cmd instance which can be used to run a single command.
|
||||
// This follows the pattern of package os/exec.
|
||||
Command(cmd string, args ...string) Cmd
|
||||
|
||||
// CommandContext returns a Cmd instance which can be used to run a single command.
|
||||
//
|
||||
// The provided context is used to kill the process if the context becomes done
|
||||
// before the command completes on its own. For example, a timeout can be set in
|
||||
// the context.
|
||||
CommandContext(ctx context.Context, cmd string, args ...string) Cmd
|
||||
|
||||
// LookPath wraps os/exec.LookPath
|
||||
LookPath(file string) (string, error)
|
||||
}
|
||||
|
||||
// Cmd is an interface that presents an API that is very similar to Cmd from os/exec.
|
||||
// As more functionality is needed, this can grow. Since Cmd is a struct, we will have
|
||||
// to replace fields with get/set method pairs.
|
||||
type Cmd interface {
|
||||
// Run runs the command to the completion.
|
||||
Run() error
|
||||
// CombinedOutput runs the command and returns its combined standard output
|
||||
// and standard error. This follows the pattern of package os/exec.
|
||||
CombinedOutput() ([]byte, error)
|
||||
// Output runs the command and returns standard output, but not standard err
|
||||
Output() ([]byte, error)
|
||||
SetDir(dir string)
|
||||
SetStdin(in io.Reader)
|
||||
SetStdout(out io.Writer)
|
||||
SetStderr(out io.Writer)
|
||||
// Stops the command by sending SIGTERM. It is not guaranteed the
|
||||
// process will stop before this function returns. If the process is not
|
||||
// responding, an internal timer function will send a SIGKILL to force
|
||||
// terminate after 10 seconds.
|
||||
Stop()
|
||||
}
|
||||
|
||||
// ExitError is an interface that presents an API similar to os.ProcessState, which is
|
||||
// what ExitError from os/exec is. This is designed to make testing a bit easier and
|
||||
// probably loses some of the cross-platform properties of the underlying library.
|
||||
type ExitError interface {
|
||||
String() string
|
||||
Error() string
|
||||
Exited() bool
|
||||
ExitStatus() int
|
||||
}
|
||||
|
||||
// Implements Interface in terms of really exec()ing.
|
||||
type executor struct{}
|
||||
|
||||
// New returns a new Interface which will os/exec to run commands.
|
||||
func New() Interface {
|
||||
return &executor{}
|
||||
}
|
||||
|
||||
// Command is part of the Interface interface.
|
||||
func (executor *executor) Command(cmd string, args ...string) Cmd {
|
||||
return (*cmdWrapper)(osexec.Command(cmd, args...))
|
||||
}
|
||||
|
||||
// CommandContext is part of the Interface interface.
|
||||
func (executor *executor) CommandContext(ctx context.Context, cmd string, args ...string) Cmd {
|
||||
return (*cmdWrapper)(osexec.CommandContext(ctx, cmd, args...))
|
||||
}
|
||||
|
||||
// LookPath is part of the Interface interface
|
||||
func (executor *executor) LookPath(file string) (string, error) {
|
||||
return osexec.LookPath(file)
|
||||
}
|
||||
|
||||
// Wraps exec.Cmd so we can capture errors.
|
||||
type cmdWrapper osexec.Cmd
|
||||
|
||||
var _ Cmd = &cmdWrapper{}
|
||||
|
||||
func (cmd *cmdWrapper) SetDir(dir string) {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
|
||||
func (cmd *cmdWrapper) SetStdin(in io.Reader) {
|
||||
cmd.Stdin = in
|
||||
}
|
||||
|
||||
func (cmd *cmdWrapper) SetStdout(out io.Writer) {
|
||||
cmd.Stdout = out
|
||||
}
|
||||
|
||||
func (cmd *cmdWrapper) SetStderr(out io.Writer) {
|
||||
cmd.Stderr = out
|
||||
}
|
||||
|
||||
// Run is part of the Cmd interface.
|
||||
func (cmd *cmdWrapper) Run() error {
|
||||
err := (*osexec.Cmd)(cmd).Run()
|
||||
return handleError(err)
|
||||
}
|
||||
|
||||
// CombinedOutput is part of the Cmd interface.
|
||||
func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) {
|
||||
out, err := (*osexec.Cmd)(cmd).CombinedOutput()
|
||||
return out, handleError(err)
|
||||
}
|
||||
|
||||
func (cmd *cmdWrapper) Output() ([]byte, error) {
|
||||
out, err := (*osexec.Cmd)(cmd).Output()
|
||||
return out, handleError(err)
|
||||
}
|
||||
|
||||
// Stop is part of the Cmd interface.
|
||||
func (cmd *cmdWrapper) Stop() {
|
||||
c := (*osexec.Cmd)(cmd)
|
||||
|
||||
if c.Process == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Process.Signal(syscall.SIGTERM)
|
||||
|
||||
time.AfterFunc(10*time.Second, func() {
|
||||
if !c.ProcessState.Exited() {
|
||||
c.Process.Signal(syscall.SIGKILL)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handleError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch e := err.(type) {
|
||||
case *osexec.ExitError:
|
||||
return &ExitErrorWrapper{e}
|
||||
case *osexec.Error:
|
||||
if e.Err == osexec.ErrNotFound {
|
||||
return ErrExecutableNotFound
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ExitErrorWrapper is an implementation of ExitError in terms of os/exec ExitError.
|
||||
// Note: standard exec.ExitError is type *os.ProcessState, which already implements Exited().
|
||||
type ExitErrorWrapper struct {
|
||||
*osexec.ExitError
|
||||
}
|
||||
|
||||
var _ ExitError = &ExitErrorWrapper{}
|
||||
|
||||
// ExitStatus is part of the ExitError interface.
|
||||
func (eew ExitErrorWrapper) ExitStatus() int {
|
||||
ws, ok := eew.Sys().(syscall.WaitStatus)
|
||||
if !ok {
|
||||
panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper")
|
||||
}
|
||||
return ws.ExitStatus()
|
||||
}
|
||||
|
||||
// CodeExitError is an implementation of ExitError consisting of an error object
|
||||
// and an exit code (the upper bits of os.exec.ExitStatus).
|
||||
type CodeExitError struct {
|
||||
Err error
|
||||
Code int
|
||||
}
|
||||
|
||||
var _ ExitError = CodeExitError{}
|
||||
|
||||
func (e CodeExitError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e CodeExitError) String() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e CodeExitError) Exited() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e CodeExitError) ExitStatus() int {
|
||||
return e.Code
|
||||
}
|
||||
@@ -7,8 +7,11 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
)
|
||||
|
||||
var p Provider
|
||||
@@ -28,6 +31,7 @@ func ApiserverStart(provider Provider) {
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/containerLogs/{namespace}/{pod}/{container}", ApiServerHandler).Methods("GET")
|
||||
r.HandleFunc("/exec/{namespace}/{pod}/{container}", ApiServerHandlerExec).Methods("POST")
|
||||
r.NotFoundHandler = http.HandlerFunc(NotFound)
|
||||
|
||||
if err := http.ListenAndServeTLS(addr, certFilePath, keyFilePath, r); err != nil {
|
||||
@@ -64,3 +68,36 @@ func ApiServerHandler(w http.ResponseWriter, req *http.Request) {
|
||||
NotFound(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func ApiServerHandlerExec(w http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
|
||||
namespace := vars["namespace"]
|
||||
pod := vars["pod"]
|
||||
container := vars["container"]
|
||||
|
||||
supportedStreamProtocols := strings.Split(req.Header.Get("X-Stream-Protocol-Version"), ",")
|
||||
|
||||
q := req.URL.Query()
|
||||
command := q.Get("command")
|
||||
|
||||
// streamOpts := &remotecommand.Options{
|
||||
// Stdin: (q.Get("input") == "1"),
|
||||
// Stdout: (q.Get("output") == "1"),
|
||||
// Stderr: (q.Get("error") == "1"),
|
||||
// TTY: (q.Get("tty") == "1"),
|
||||
// }
|
||||
|
||||
// TODO: tty flag causes remotecommand.createStreams to wait for the wrong number of streams
|
||||
streamOpts := &remotecommand.Options{
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
TTY: false,
|
||||
}
|
||||
|
||||
idleTimeout := time.Second * 30
|
||||
streamCreationTimeout := time.Second * 30
|
||||
|
||||
remotecommand.ServeExec(w, req, p, fmt.Sprintf("%s-%s", namespace, pod), "", container, []string{command}, streamOpts, idleTimeout, streamCreationTimeout, supportedStreamProtocols)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package vkubelet
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/aws"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azurebatch"
|
||||
@@ -8,6 +11,8 @@ import (
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/hypersh"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/web"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// Compile time proof that our implementations meet the Provider interface.
|
||||
@@ -35,6 +40,10 @@ type Provider interface {
|
||||
// GetContainerLogs retrieves the logs of a container by name from the provider.
|
||||
GetContainerLogs(namespace, podName, containerName string, tail int) (string, error)
|
||||
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
|
||||
// GetPodStatus retrieves the status of a pod by name from the provider.
|
||||
GetPodStatus(namespace, name string) (*v1.PodStatus, error)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user