Команда exec каждую минуту и ​​обслуживает вывод через http - PullRequest
1 голос
/ 13 октября 2019

Я хочу запускать команду в оболочке Bash каждую минуту и ​​обрабатывать вывод через http: http://localhost:8080/feed

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := `<a piped command>`
    out, err := exec.Command("bash", "-c", cmd).Output()
    if err != nil {
        fmt.Sprintf("Failed to execute command: %s", cmd)
    }
    fmt.Println(string(out))
}

ОБНОВЛЕНИЕ:

package main

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

func handler(w http.ResponseWriter, r *http.Request) {
    cmd := `<a piped command>`
    out, err := exec.Command("bash", "-c", cmd).Output()
    if err != nil {
        fmt.Sprintf("Failed to execute command: %s", cmd)
    }
    fmt.Fprintf(w, string(out))
}

func main() {
    http.HandleFunc("/feed", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

С помощью приведенного выше кода команда запускается каждый раз при обращении к http://localhost:8080/feed. Как заставить его кэшировать (?) Выходные данные команды на одну минуту, а затем снова запускать команду только после истечения времени кэширования?

Ответы [ 3 ]

1 голос
/ 13 октября 2019

Если вывод не слишком велик, вы можете сохранить его в переменной памяти. Я решил подождать результата в случае выполнения сценария (с использованием мьютекса):

package main

import (
  "fmt"
  "net/http"
  "os/exec"
  "sync"
  "time"
)   

var LoopDelay = 60*time.Second

type Output struct {
  sync.Mutex
  content string
}   


func main() {

  var output *Output = new(Output)

  go updateResult(output)

  http.HandleFunc("/feed", initHandle(output))
  err := http.ListenAndServe(":8080", nil)
  if err != nil {
    fmt.Println("ERROR", err)
  }   
}   

func initHandle(output *Output) func(http.ResponseWriter, *http.Request) {
  return func(respw http.ResponseWriter, req *http.Request) {
    output.Lock()
    defer output.Unlock()
    _, err := respw.Write([]byte(output.content))
    if err != nil {
        fmt.Println("ERROR: Unable to write response: ", err)
    }
  }
}

func updateResult(output *Output) {
  var execFn = func() { /* Extracted so that 'defer' executes at the end of loop iteration */
    output.Lock()
    defer output.Unlock()
    command := exec.Command("bash", "-c", "date | nl ")
    output1, err := command.CombinedOutput()
    if err != nil {
      output.content = err.Error()
    } else {
      output.content = string(output1)
    }   

  }   
  for {
    execFn()
    time.Sleep(LoopDelay)
  }
}

Выполнение

date; curl http://localhost:8080/feed

дает вывод (при нескольких вызовах):

 1  dimanche 13 octobre 2019, 09:41:40 (UTC+0200)
http-server-cmd-output> date; curl http://localhost:8080/feed

dimanche 13 octobre 2019, 09:42:05 (UTC+0200)
 1  dimanche 13 octobre 2019, 09:41:40 (UTC+0200)

Несколько вещей, которые следует учитывать:
- Использовано 'дата |nl 'as команда с примером канала
- Если вывод слишком большой, записать в файл
- Скорее всего, рекомендуется оставить мьютекс только для содержимого update (не нужно ждать во время выполнения скрипта) - вы можете попытаться улучшить его
- В подпрограмме Go может быть переменная (канал) для выхода по сигналу (например, когда программа заканчивается)

EDIT: переменная перемещена в основную функцию

1 голос
/ 13 октября 2019

Как сделать так, чтобы она кэшировала (?) Выходные данные команды в течение одной минуты, а затем снова запускала команду только после истечения времени кэширования?

В приведенном ниже решениидве горутины объявлены.

  • Первый цикл выполнения, пока не будет задан контекст для выполнения команды через регулярные промежутки времени и отправки копии второй подпрограмме go.
  • Вторая подпрограмма пытается получить из первойили распределяет его значение между другими программами.
  • Обработчик http, третья программа, только запрашивает значение у получателя и что-то с ним делает.

Причина использования трехподпрограммы вместо двух в этом примере должны предотвратить блокировку подпрограмм http, если команда выполняется. С этими дополнительными процедурами http-запросы ждут только синхронизации.

type state - это фиктивная структура для передачи значений внутри каналов.

Мы предотвращаем условия гонки из-за двух фактов,состояние передается по значению, cmd.Output() выделяет новый буфер при каждом запуске.

Чтобы получить исходную команду в обработчике http, OP должен создать пользовательский тип ошибки и прикрепить эту информацию к записанной ошибке,в http-обработчике OP должен набрать assert для своего пользовательского типа и получить оттуда конкретные сведения.

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os/exec"
    "time"
)

type state struct {
    out []byte
    err error
}

func main() {

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    set := make(chan state, 1)
    go func() {
        ticker := time.NewTicker(2 * time.Second)
        cmd := []string{"date"}
        s := state{}
        s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
        set <- s
        for {
            select {
            case <-ctx.Done():
                return
            case <-ticker.C:
                s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
                set <- s
            }
        }
    }()

    get := make(chan state)
    go func() {
        s := state{}
        for {
            select {
            case <-ctx.Done():
                return
            case s = <-set: // get the fresh value, if any
            case get <- s: // distribute your copy
            }
        }
    }()

    http.HandleFunc("/feed", func(w http.ResponseWriter, r *http.Request) {
        state := <-get
        if state.err != nil {
            fmt.Printf("Failed to execute command: %v", state.err)
        }
        fmt.Fprintf(w, string(state.out))
    })
    log.Fatal(http.ListenAndServe(":8080", nil))
}
0 голосов
/ 13 октября 2019

Вы можете:

Таким образом, каждый раз, когда вы заходите на свой сервер, вы получите последний вывод команды.

Как сделать так, чтобы он кэшировал (?) Выходные данныекоманду на одну минуту, а затем запустить команду снова только после истечения времени кеширования?

Сохраняя выполнение команды отдельно от обслуживаемого вывода команды.

Вот почему вы должны поддерживать два асинхронных, как правило, через goroutine, aa time.NewTicker.
См. " Есть ли способ выполнять повторяющиеся задачи с интервалами?"

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...