Use la tubería Golang para realizar una interacción remota

Este artículo presenta la tubería Golang y su aplicación en diferentes escenarios.

Introducción

Pipe implementa la redirección de un proceso a otro, es un canal de datos bidireccional para la comunicación entre procesos.
La función io.Pipe crea un canal de sincronización de memoria para conectar io.Reader e io.Writer. El entorno de uso de ejemplo de este artículo es:

go version
go version go1.19.3 linux/amd64

Go pipe ejemplo simple

Antes de implementar la interacción remota, echemos un vistazo al siguiente ejemplo simple para demostrar cómo usar la función io.Pipe:

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    r, w := io.Pipe()

    go func() {
        fmt.Fprint(w, "Hello there\n")
        w.Close()
    }()

    _, err := io.Copy(os.Stdout, r)

    if err != nil {
        log.Fatal(err)
    }
}

Primero cree la tubería, luego escriba los datos en el escritor de la tubería en la corrutina y luego use la función io.Copy para copiar los datos del Lector de la tubería a la salida estándar:

go func() {
    fmt.Fprint(w, "Hello there\n")
    w.Close()
}()

La escritura de datos en la rutina se debe a que PipeWriter se bloquea cada vez que se escribe hasta que PipeReader consume los datos por completo.
Ejecuta el programa:

go run main.go 
Hello there

A través de este ejemplo simple, se demuestra la capacidad de la redirección de canalización. Después de comprender este principio básico, veamos primero la canalización de los comandos de Shell. Al final, nuestro objetivo es realizar una interacción de línea de comando remota a través de WEB.

Ir cmd StdoutPipe

StdoutPipe de Cmd devuelve la salida estándar del comando canalizado cuando se inicia el comando:

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "os/exec"
)

func main() {

    cmd := exec.Command("ping", "www.baidu.com")
    stdout, err := cmd.StdoutPipe()

    if err != nil {
        log.Fatal(err)
    }

    cmd.Start()

    buf := bufio.NewReader(stdout) 
    num := 0

    for {
        line, _, _ := buf.ReadLine()
        if num > 3 {
            os.Exit(0)
        }
        num += 1
        fmt.Println(string(line))
    }
}

El código anterior inicia el comando ping y luego lee 4 líneas de su salida.Esta línea de código inicia el comando ping:

    cmd := exec.Command("ping", "www.baidu.com")
    stdout, err := cmd.StdoutPipe()
    buf := bufio.NewReader(stdout) 

Luego obtenga la salida estándar del comando y guarde la salida en buf. Lo siguiente lee 4 líneas del búfer:

for {
    line, _, _ := buf.ReadLine()
    if num > 3 {
        os.Exit(0)
    }
    num += 1
    fmt.Println(string(line))
}

Lea 4 líneas y salida a la consola, ejecute el programa, la salida es la siguiente:

go run main.go
PING www.a.shifen.com (180.101.50.188) 56(84) bytes of data.
64 bytes from 180.101.50.188 (180.101.50.188): icmp_seq=1 ttl=53 time=12.0 ms
64 bytes from 180.101.50.188 (180.101.50.188): icmp_seq=2 ttl=53 time=11.2 ms
64 bytes from 180.101.50.188 (180.101.50.188): icmp_seq=3 ttl=53 time=10.5 ms

A través de este ejemplo, el resultado de la ejecución del comando se captura con éxito en el búfer, y la lógica de procesamiento se puede agregar y luego enviar a la consola.

Las canalizaciones se utilizan en el procesamiento de solicitudes http

El siguiente ejemplo muestra el uso de canalizaciones en el procesamiento de solicitudes http. Ejecute el comando de fecha y emita el resultado a través de HTTP, para que pueda ver el resultado de la ejecución del comando desde el meta esclavo.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os/exec"
)

func handler(w http.ResponseWriter, r *http.Request) {

    cmd := exec.Command("date")

    pr, pw := io.Pipe()
    defer pw.Close()

    cmd.Stdout = pw
    cmd.Stderr = pw
    go io.Copy(w, pr)

    cmd.Run()
}

func main() {

    http.HandleFunc("/", handler)
    fmt.Println("server started on port 8080")
    http.ListenAndServe(":8080", nil)
}

El código clave es crear una canalización y asignar PipeWriter a la salida estándar y al error estándar del comando.

cmd := exec.Command("date")
pr, pw := io.Pipe()
defer pw.Close()
cmd.Stdout = pw
cmd.Stderr = pw

go io.Copy(w, pr)

Luego copie PipeReader a http.ResponseWriter en la corrutina.Finalmente, ejecute el programa para ver el resultado:

$ go run handler.go 
server started on port 8080

Use curl o el navegador para acceder a la dirección: localhost:8080, puede ver:

2023年 02月 22日 星期三 17:06:11 CST

Modifique el programa anterior y use el comando como parámetro para realizar una interacción remota. Veamos cómo usar la canalización para escribir datos en el terminal de entrada, incluida la entrada estándar de solicitudes y comandos http.

Usar canalización para enviar datos json de solicitud posterior

El siguiente ejemplo https://httpbin.org/postenvía datos json a la dirección de la solicitud como cuerpo de la solicitud.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
)

type PayLoad struct {
    Content string
}

func main() {

    r, w := io.Pipe()

    go func() {
        defer w.Close()

        err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"})

        if err != nil {
            log.Fatal(err)
        }
    }()

    resp, err := http.Post("https://httpbin.org/post", "application/json", r)

    if err != nil {
        log.Fatal(err)
    }

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(body))
}

El ejemplo anterior realiza el envío de datos json a la solicitud de publicación y la lectura del contenido de la respuesta.

Primero defina la canalización y luego escriba los datos json en el Escritor de canalización en la corrutina:

go func() {
    defer w.Close()

    err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"})

    if err != nil {
        log.Fatal(err)
    }
}()

A continuación, pase el lector de canalización a la solicitud como parámetro:

resp, err := http.Post("https://httpbin.org/post", "application/json", r)

Finalmente, lea el contenido de la respuesta:

body, err := ioutil.ReadAll(resp.Body)

if err != nil {
    log.Fatal(err)
}

fmt.Println(string(body))

Ejecute la salida del programa:

go run main.go
{
  "args": {}, 
  "data": "{\"Content\":\"Hello there!\"}\n", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "Transfer-Encoding": "chunked", 
    "User-Agent": "Go-http-client/2.0", 
    "X-Amzn-Trace-Id": "Root=1-63f5c8c6-4a14ee9a2dc14e352f234fae"
  }, 
  // 省略...
}

Lectura de entrada estándar a través de una tubería

El siguiente ejemplo usa la canalización para leer datos de la entrada estándar e imprimir los datos y la cantidad de bytes y bloques de datos:

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "os"
)

func main() {

    nBytes, nChunks := int64(0), int64(0)
    r := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 4*1024)

    for {

        n, err := r.Read(buf[:cap(buf)])
        buf = buf[:n]

        if n == 0 {

            if err == nil {
                continue
            }

            if err == io.EOF {
                break
            }

            log.Fatal(err)
        }

        nChunks++
        nBytes += int64(len(buf))

        fmt.Println(string(buf))

        if err != nil && err != io.EOF {
            log.Fatal(err)
        }
    }

    fmt.Println("Bytes:", nBytes, "Chunks:", nChunks)
}

Primero defina el lector de entrada estándar del contenedor:

r := bufio.NewReader(os.Stdin)

buf := make([]byte, 0, 4*1024)
n, err := r.Read(buf[:cap(buf)])
buf = buf[:n]

nChunks++
nBytes += int64(len(buf))
fmt.Println(string(buf))

Luego cree un búfer de 4kb y lea los datos de la entrada estándar en el búfer. Luego calcule el número de bloques y bytes, y finalmente acepte el contenido del búfer.

date | go run main.go
2023年 02月 22日 星期三 16:08:17 CST

Bytes: 43 Chunks: 1

Aquí, |la salida del comando de fecha se pasa a través de la operación y el contenido mostrado es consistente con las expectativas.

Ir a las estadísticas

La función Stat devuelve la estructura FileInfo, que describe la información del archivo. Podemos usarlo para comprobar si los datos provienen del terminal.

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    stat, _ := os.Stdin.Stat()

    if (stat.Mode() & os.ModeCharDevice) == 0 {

        var buf []byte
        scanner := bufio.NewScanner(os.Stdin)

        for scanner.Scan() {
            buf = append(buf, scanner.Bytes()...)
        }

        if err := scanner.Err(); err != nil {
            log.Fatal(err)
        }

        fmt.Printf("Hello %s!\n", buf)

    } else {
        fmt.Print("Enter your name: ")

        var name string
        fmt.Scanf("%s", &name)
        fmt.Printf("Hello %s!\n", name)
    }
}

Los datos de este ejemplo pueden provenir de una terminal o una tubería. Para juzgar, obtenga la estadística a través del siguiente código:

stat, _ := os.Stdin.Stat()

Juicio después de obtener la estructura FileInfo de la entrada estándar:

if (stat.Mode() & os.ModeCharDevice) == 0 {

Esta línea juzga que los datos provienen de la tubería, de lo contrario, es la terminal. Es decir, si ninguna canalización proporciona datos, se solicitan datos al usuario. Ejecuta el programa:

$ echo "golang" | go run main.go
Hello golang!

$go run main.go
Enter your name: java
Hello java!

Resumir

Este artículo presenta el uso de la canalización de Golang. Además de realizar la interacción de comandos remotos, también presenta la obtención de contenido de entrada estándar y la evaluación del origen de los datos de entrada estándar. Los lectores podrán escribir aplicaciones geniales combinando estos ejemplos simples.

Supongo que te gusta

Origin blog.csdn.net/neweastsun/article/details/129176591
Recomendado
Clasificación