I built a simple web app with a VueJS front (client) and a Go back (server), using gRPC and protobuf.
In order to communicate, an envoy proxy has to be set up between them to convert web client HTTP/1.1 to HTTP/2.
I have no issue deploying this app on Linux, and have dockerized all three services.
However, I cannot manage to get my services to communicate on MacOS.
The app repository is available here: https://github.com/42Projects/nPuzzle
The deploy folder contains the docker-compose file, the envoy.yaml file required for the envoy container, as well as all three dockerfiles.
On MacOS the last line of the envoy.yaml needs to be updated, localhost has to be changed to host.docker.internal.
The client contains a ping function (in client/src/App.vue), the window.location.hostname might need to be changed to the docker-machine IP, unsure about that, my tries made no difference:
created () {
this.client = new NpuzzleClient('http://' + window.location.hostname + ':8080', null, null);
const ping = () => {
let message = new Message();
message.setMessage('ping');
this.client.greets(message, {}, err => this.serverOnline = !err);
};
ping();
window.setInterval(ping, 1000);
},
which is executed every second, and will display that the server is online on the top right corner. I have been so far unsuccessful in reaching my server while deploying on MacOS. It works perfectly fine on Linux.
Here is the code for the go server, I think it is correct to listen on localhost:9090 as the server will be dockerized, but I might be wrong and the address might need to be changed.
package main
import (
"context"
"flag"
"fmt"
pb "github.com/42Projects/nPuzzle/proto"
npuzzle "github.com/42Projects/nPuzzle/src"
"google.golang.org/grpc"
"log"
"net"
"time"
)
var port = flag.Int("port", 9090, "the server port")
type server struct{}
func (s *server) Greets(ctx context.Context, message *pb.Message) (*pb.Message, error) {
return &pb.Message{Message: "pong!"}, nil
}
func (s *server) Parse(ctx context.Context, message *pb.Message) (*pb.Matrix, error) {
log.Printf("received parsing request: %#v", message.Message)
m, err := npuzzle.ParseMatrix(message.Message)
if err != nil {
log.Println(err)
return &pb.Matrix{
Success: false,
Error: err.Error(),
}, nil
}
rows := make([]*pb.Matrix_Row, len(m))
for index, row := range m {
/* We need unsigned 32bits integer for protobuf */
uIntRow := make([]uint32, len(m))
for rowIndex, num := range row {
uIntRow[rowIndex] = uint32(num)
}
rows[index] = &pb.Matrix_Row{Num: uIntRow}
}
return &pb.Matrix{
Success: true,
Rows: rows,
}, nil
}
func (s *server) Solve(ctx context.Context, problem *pb.Problem) (*pb.Result, error) {
/* Choose heuristic function */
var heuristic npuzzle.Heuristic
switch problem.Heuristic {
case "hamming":
heuristic = npuzzle.HammingDistance
case "manhattan":
heuristic = npuzzle.ManhattanDistance
case "manhattan + linear conflicts":
heuristic = npuzzle.ManhattanPlusLinearConflicts
}
/* Choose between greedy search and uniform-cost search */
var goal npuzzle.Goal
var search npuzzle.Search
switch problem.Search {
case "greedy":
goal = npuzzle.GreedyGoalReached
search = npuzzle.GreedySearch
case "uniform-cost":
goal = npuzzle.UniformCostGoalReached
search = npuzzle.UniformCostSearch
}
/* Convert protobuf unsigned 32bits integer to regular integer */
size := len(problem.Rows)
m := make(npuzzle.Matrix, size)
for y, row := range problem.Rows {
m[y] = make([]int, size)
for x, num := range row.Num {
m[y][x] = int(num)
}
}
log.Printf("received problem:\n - heuristic: %v\n - search: %v\n - matrix: %v\n", problem.Heuristic, problem.Search, m)
if npuzzle.IsSolvable(m) == false {
log.Println("failed to solve problem: unsolvable")
return &pb.Result{
Success: false,
Error: "unsolvable",
}, nil
}
begin := time.Now()
log.Printf("starting solve on %v...", m)
res, totalNumberOfStates, maxNumberOfStates, err := m.Solve(heuristic, search, goal, 30*time.Second)
if err != nil {
log.Printf("timed ouf after %v", 30*time.Second)
return &pb.Result{
Success: false,
Error: fmt.Sprintf("timed ouf after %v", 30*time.Second),
}, nil
}
duration := time.Since(begin)
log.Printf("solved %v in %v seconds", m, duration)
var path string
if res.Parent == nil {
path = "already solved!"
} else {
path = npuzzle.StringifyPath(res)
}
return &pb.Result{
Success: true,
Time: duration.String(),
Moves: int32(res.Cost),
TotalStates: int32(totalNumberOfStates),
MaxStates: int32(maxNumberOfStates),
Path: path,
}, nil
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v\n", err)
}
s := grpc.NewServer()
pb.RegisterNpuzzleServer(s, &server{})
log.Printf("starting server on port %v\n", *port)
log.Fatalf("failed to serve: %v\n", s.Serve(lis))
}
I tried to launch the client and the server locally, and only containerize the envoy proxy, while trying different addresses (my docker machine IP address and localhost), but it didn't work. I also tried to launch all three containers but no result here either.
I'm unsure what to change to successfully manage to make my app work properly on MacOS.
Related
I am trying to code a Docker Monitoring software in Golang.
my Code looks as followed:
package main
import (
"bytes"
"context"
"fmt"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
func main() {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
if err != nil {
panic(err)
}
for _, container := range containers {
out, err := cli.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{
ShowStderr: true,
ShowStdout: true,
Timestamps: false,
Follow: true,
Tail: "40"})
if err != nil {
panic(err)
}
fmt.Println("The \"" + container.Image + "\" container, with the ID \"" + container.ID + "\" logged: ")
fmt.Println()
buf := new(bytes.Buffer)
fmt.Println(buf.ReadFrom(out))
fmt.Println(buf.String())
}
time.Sleep(time.Second * 3)
}
The problem is that the execution of the above code stops on the fmt.Println(buf.ReadFrom(out)) statement. The code used to work, but it suddenly just doesn't anymore. Either it stops without an error, or it returns an empty String.
The client I am trying to collect the logs from is also coded by myself, and it looks like follows:
package main
import (
"log"
"time"
)
func main() {
for i := 0; i > -1; i++ {
log.Output(1, "Hello World logged!")
time.Sleep(time.Minute)
}
}
I already tried debugging and checking Variables, but I just can't get to the source of the Problem.
I am really not sure as I don't have any error logs to confirm my hypothesis.
But could it be the case that as the ContainerLogs returns a stream (io.ReadCloser), maybe the stream itself hasn't closed yet?
If this is a possibility, you can either do a dry run first to test out this theory by adding a timeout and logging it after every small duration ?
one possible way to do this is
select {
case <-time.After(5 * time.Second):
fmt.Println("Timeout exceeded while reading container logs")
case <-ctx.Done():
fmt.Println("Context cancelled while reading container logs")
case b := <-out:
if b != nil {
buf.Write(b)
}
}
Is there a way to set Host in grpc-dart with with insecure connection, I have looked at this but can not find a way?
Just need to set: authority
It should be similar to this in go - example setting host serverHostOverride:(note I can set host AND run WithInsecure connection)
var (
serverAddr = flag.String("server_addr", "127.0.0.1:8080", "The server address in the format of host:port")
serverHostOverride = flag.String("server_host_override", "", "")
insecure = flag.Bool("insecure", false, "Set to true to skip SSL validation")
)
func main() {
flag.Parse()
var opts []grpc.DialOption
if *serverHostOverride != "" {
opts = append(opts, grpc.WithAuthority(*serverHostOverride))
}
if *insecure {
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
client := pb.NewPingServiceClient(conn)
ping(client, "hello")
pingStream(client, "hello")
}
I want make a web terminal by kubernetes client-go api, and now i meet some problem.
main:
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/ws", echo)
log.Fatal(http.ListenAndServe(*addr, nil))
}
struct&handleFunc:
type Cmd struct {
Conn *websocket.Conn
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
func echo(w http.ResponseWriter, r *http.Request) {
cmd := Cmd{}
cmd.Stderr = ioutil.Discard
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
cmd.Conn = c
cmd.handleClient()
}
func (c *Cmd) handleClient() {
//c.Stdout = os.Stdout
//c.Stdin = os.Stdin
var read io.Reader
var write io.Writer
c.Stdin = read
c.Stdout = write
go func(rd io.Reader) {
for {
_, r, err := c.Conn.NextReader()
if err != nil {
fmt.Println("ReadErr:", err)
c.Conn.Close()
return
}
rd = r
}
}(read)
go func(wr io.Writer) {
w, err := c.Conn.NextWriter(1)
if err != nil {
fmt.Println("WriteErr:", err)
c.Conn.Close()
return
}
wr = w
}(write)
err := api.ExecOperator{}.ExecConsoleInContainer("1", "nginx-65899c769f-mkswf", c.Stdin, c.Stdout, c.Stderr)
if err != nil {
fmt.Println("ExecErr:", err)
return
}
}
api.ExecOperator{}.ExecConsoleInContainer() is a func invoke kubernetes exec api like kubernetes exec_util.go
When i use the code below,i can operate at the console,but i dont know how to use websocket(gorilla/websocket) input and output to replace os.stdin and os.stdout,i tried to write some of the code above ,but it cant work.
c.Stdout = os.Stdout
c.Stdin = os.Stdin
I have trouble with memory. I don't understand why Go uses more and more memory (never freeing it) when my program runs for a long time.
After the first allocation, program uses nearly 9 MB of memory. Then after 12 hours it starts to use more memory exponentially, until 800 MB.
//.....code.....
if bol {
// Assignment Struct.Var
Struct_VastScript.TxtNoticeTop = JsonStruct_S.Options.TxtNoticeTop
Struct_VastScript.TxtNoticeBottom = JsonStruct_S.Options.TxtNoticeBottom
Struct_VastScript.Loop = JsonStruct_S.Options.Loop
Struct_Image, err := getImage(Struct_VastScript.Video)
if err == nil {
if mobile == "true" {
Struct_VastScript.Image = Struct_Image.URL360
}
}
//open and parse a template file
fi = path.Join("templates/VastPlayer", "TempVastPlayer.txt")
tmpl, err := template.ParseFiles(fi)
if err != nil {
job_1.Complete(health.Panic)
return false, err
}
//substitute fields in the template 'tmpl', with values from 'XmlStruct_V' and write it out to 'buf'
var buf bytes.Buffer
if err := tmpl.Execute(&buf, Struct_VastScript); err != nil {
//if err := tmpl.Execute(w, XmlStruct_V); err != nil {
job_1.Complete(health.Panic)
return false, err
}
// Call Func randString() : return alphanum random
dir := randString(12)
fpath := "http://creative2.xxx.io/api/html/" + dir
// Create a new EndPoint to write the generated 'template' on 'w' http.ResponseWriter
routeHtml := "/api/html/" + dir
http.HandleFunc(routeHtml, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//writes Template to 'w' http.ResponseWriter
fmt.Fprintf(w, buf.String())
fmt.Println("successfull Operation 2 !!")
fmt.Println("")
job_2.Complete(health.Success)
}))
//Call Func JsonReply(): return the finale Json response
str := JsonReply(fpath, JsonStruct_S.Options.Animated, JsonStruct_S.Options.Responsive, JsonStruct_S.Options.Clickurl, JsonStruct_S.Options.Width, JsonStruct_S.Options.Height, adid, campaignid, JsonStruct_S.Type, JsonStruct_S.Options.Aspectratio, mobile)
w.Header().Set("Content-Type", "application/json")
//writes FinaleJson to 'w' http.ResponseWriter(it contains the link of the second endpoint "/api/html/")
fmt.Fprint(w, str)
fmt.Println("successfull Operation !!")
fmt.Println("")
job_1.Complete(health.Success)
return true, nil
} else {
return false, nil
}
For each call,my service need to generate a new template with the params that I receive,as you see I create a new endpoint for each call, I don't know if it's a good idea, I think the problem comes from this part of code but Im not sure because I don't know how GO manage it.
Obviously, you should not create handler every time request appears. They never free the memory so you will end up having out of memory exception.
Instead, put the handler endpoint into array (slice) and use ONE handler that responds to the request by looking the URL in this slice and then removing the item from the slice with it is not needed any longer.
So basically, instead of
routeHtml := "/api/html/" + dir
http.HandleFunc(routeHtml, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//writes Template to 'w' http.ResponseWriter
fmt.Fprintf(w, buf.String())
fmt.Println("successfull Operation 2 !!")
fmt.Println("")
job_2.Complete(health.Success)
}))
do
type JobInfo struct {
Path string
// some data here
}
// maybe global context
var jobs []JobInfo
// initialisation
jobs = make([]JobInfo, 0)
http.HandleFunc("/api/html/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
var job *JobInfo
for _, j := range jobs {
if j.Path == path {
job = &j
break
}
}
if job != nil {
// handle job request here
}
}))
// and then in the jobs' loop
handlers = append(handlers, JobInfo{"/api/html/" + dir, ...})
It will work because:
Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees, like "/images/" (note the trailing slash). Longer patterns take precedence over shorter ones, so that if there are handlers registered for both "/images/" and "/images/thumbnails/", the latter handler will be called for paths beginning "/images/thumbnails/" and the former will receive requests for any other paths in the "/images/" subtree.
Do not forget to clean the array jobs, of course.
Instead of using slice it's better to use map
type JobInfo struct {
Path string
// some data here
}
// global context
var jobs map[string]JobInfo
// initialisation
jobs = make(map[string]JobInfoStruct)
http.HandleFunc("/api/html/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
var job JobInfoStruct
var ok bool
job, ok = jobs[path]
if ok {
// handle job request here
//then after delete the job
delete(jobs, path)
}
}))
// and then in the jobs' loop
pathVideo := "/api/html/" + dir
jobs[pathVideo] = JobInfoStruct{pathVideo, ...}
I've successfully used jsctypes in the past but the latest version of firefox (32) has started to give me an odd error message
here is what used to work-
javascript content sends a message to a javascript extension in chrome (the extension uses ctypes to call a special device allocator. it then returns a cdata.finalizer to the content
later when the content is garbage collected the finaizer gets called to release the special device allocation
while this used to work fine, I'm now getting an exception ctypes.CDataFinalizer
Not allowed to define cross-origin object as property on [Object] or [Array] XrayWrapper
searching on Google did not seem to find anything related.
on the extension I have this code( the func... things are access methods for c code)
Any suggestions?
self.addEventListener("allocArray", function (event) {
var info = event.detail.info;
try {
var cBytes = ctypes.int32_t(info.bytes);
var cArrayId = ctypes.uint32_t(0);
var err = funcAllocArray(cBytes, cArrayId.address());
if(err !== 0) {
info.rtnCode = err;
info.arrayId = -1;
info.error = "Error: " + (err === 2)? "out of memory": "allocation failed";
} else {
info.rtnCode = 0;
info.arrayId = ctypes.CDataFinalizer(cArrayId.value, funcReleaseArray);
}
}
catch(exception) {
info.rtnCode = -1;
info.arrayId = -1;
info.error = report(exception);
}
}, true, true);