将golang代码与基于浏览器的纯JS连接起来

huangapple go评论86阅读模式
英文:

Connecting a golang code with browser based vanilla JS

问题

我正在尝试使用json-rpc将基于浏览器的纯JS与golang代码连接起来,但我一直收到TypeError: Failed to fetch的错误。

这是代码:

Go中的服务器:

package main

import (
    "log"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type HelloService struct{}

func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "Hello " + request
    return nil
}

func main() {
    rpc.RegisterName("HelloService", new(HelloService))
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal("ListenTCP error: ", err)
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Fatal("Accept error: ", err)
        }
        log.Printf("New connection: %+v\n", conn.RemoteAddr())
        go jsonrpc.ServeConn(conn)
    }
}

浏览器中的客户端 client.html:

<!DOCTYPE html>
<html>
<body>
<p id="demo">Fetch a file to change this text.</p>
<script>

fetch('http://127.0.0.1:1234/rpc/v1', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '1.0',
      method: `HelloService.Hello`,
      params: ["popcontent"],//[],
      id: "jsonrpc"
    })
  })
.then((response) => {
  if (response.ok) {
    return response.json();
  }
  throw new Error('Something went wrong');
})
.then((responseJson) => {
  console.log("app :",error)
})
.catch((error) => {
  console.log("app error:",error)
});


</script>
</body>
</html>

我能够在本地连接JS代码和Go代码吗?
如果我可以在本地运行两者,我该如何修复代码?

英文:

I am trying to connect a golang code with browser based vanilla JS in the browser using json-rpc yet I keep getting TypeError: Failed to fetch

Here is the code

The server in go

package main

import (
        &quot;log&quot;
        &quot;net&quot;
        &quot;net/rpc&quot;
        &quot;net/rpc/jsonrpc&quot;
)

type HelloService struct{}

func (p *HelloService) Hello(request string, reply *string) error {
        *reply = &quot;Hello &quot; + request
        return nil
}

func main() {
        rpc.RegisterName(&quot;HelloService&quot;, new(HelloService))
        listener, err := net.Listen(&quot;tcp&quot;, &quot;:1234&quot;)
        if err != nil {
                log.Fatal(&quot;ListenTCP error: &quot;, err)
        }
        for {
                conn, err := listener.Accept()
                if err != nil {
                        log.Fatal(&quot;Accept error: &quot;, err)
                }
                log.Printf(&quot;New connection: %+v\n&quot;, conn.RemoteAddr())
                go jsonrpc.ServeConn(conn)
        }
}

The client in the browser client.html

`

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
&lt;p id=&quot;demo&quot;&gt;Fetch a file to change this text.&lt;/p&gt;
&lt;script&gt;

fetch(&#39;http://127.0.0.1:1234/rpc/v1&#39;, {
    method: &#39;POST&#39;,
    headers: {
      &#39;Content-Type&#39;: &#39;application/json&#39;,
    },
    body: JSON.stringify({
      jsonrpc: &#39;1.0&#39;,
      method: `HelloService.Hello`,
      params: [&quot;popcontent&quot;],//[],
      id: &quot;jsonrpc&quot;
    })
  })
.then((response) =&gt; {
  if (response.ok) {
    return response.json();
  }
  throw new Error(&#39;Something went wrong&#39;);
})
.then((responseJson) =&gt; {
  console.log(&quot;app :&quot;,error)
})
.catch((error) =&gt; {
  console.log(&quot;app error:&quot;,error)
});


&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

Can I even connect JS code with go code locally?
If I can run both locally, how can I fix the code?

答案1

得分: 1

创建一个帖子,并在满足特定条件时建立一个TCP连接,并将数据传递给conn.Call

例如

http.HandleFunc("/myrpc/", func(w http.ResponseWriter, r *http.Request) {
	// 检查
	{
		// 检查方法
		if r.Method != http.MethodPost {
			http.Error(w, "不允许的方法", http.StatusMethodNotAllowed)
			return
		}
		// 检查Content-Type
		// 检查授权
		// ...
	}

	// 获取用户输入
	var req RPCRequest
	{
		_ = json.NewDecoder(r.Body).Decode(&req)
	}

	conn, _ := rpc.Dial("tcp", "127.0.0.1:12345")
	defer conn.Close()

	var reply string
	if err := conn.Call(req.Method, req.Params, &reply); err != nil { // <--
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// 处理响应
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	resultBytes, _ := json.Marshal(struct {
		Msg string
	}{
		reply,
	})
	
	_, _ = fmt.Fprintln(w, string(resultBytes))
})

可运行的示例

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

type RPCRequest struct {
	Method string
	Params json.RawMessage
	Id     int
}

type Arith int

func (t *Arith) Multiply(args *json.RawMessage, reply *string) error {
	para := struct {
		A int `json:"a"`
		B int `json:"b"`
	}{}

	if err := json.Unmarshal(*args, &para); err != nil {
		return err
	}
	*reply = fmt.Sprintf("%d", para.A*para.B)
	return nil
}

func (t *Arith) unMarshal(args *json.RawMessage) (*struct{ A, B int }, error) {
	output := new(struct{ A, B int })
	err := json.Unmarshal(*args, output)
	return output, err
}

func (t *Arith) Add(args *json.RawMessage, reply *string) error {
	para, err := t.unMarshal(args)
	if err != nil {
		return err
	}
	*reply = fmt.Sprintf("%d", para.A+para.B)
	return nil
}

func NewRPCServer() (listener net.Listener, runFunc func()) {
	rpcServer := rpc.NewServer()
	// rpcServer.RegisterName("Arith", new(Arith)) // same as below
	if err := rpcServer.Register(new(Arith)); err != nil {
		log.Fatal(err)
	}

	listener, _ = net.Listen("tcp", "127.0.0.1:0")
	log.Println("[RPC SERVER]", listener.Addr().String())
	runFunc = func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Println("[listener.Accept]", err)
				return
			}

			go func(conn net.Conn) {
				defer func() {
					if err = conn.Close(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
						log.Println(err)
					}
				}()
				_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
				rpcServer.ServeConn(conn)
			}(conn)
		}
	}
	return listener, runFunc
}

var RPCServerAddr *string

func main() {
	listenerRPC, rpcServerRunFunc := NewRPCServer()
	defer func() {
		_ = listenerRPC.Close()
	}()
	go rpcServerRunFunc()

	RPCServerAddr = new(string)
	*RPCServerAddr = listenerRPC.Addr().String()
	mux := http.NewServeMux()
	mux.HandleFunc("/myrpc/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "不允许的方法", http.StatusMethodNotAllowed)
			return
		}
		if r.Header.Get("Content-Type") != "application/json" {
			http.Error(w, "内容类型错误", http.StatusBadRequest)
			return
		}

		var req RPCRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		conn, err := rpc.Dial("tcp", *RPCServerAddr)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		defer func() {
			_ = conn.Close()
		}()

		var reply string
		if err = conn.Call(req.Method, req.Params, &reply); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// 处理响应
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		resultBytes, _ := json.Marshal(struct {
			Msg   string
		}{
			reply,
		})
		if _, err = fmt.Fprintln(w, string(resultBytes)); err != nil {
			log.Println(err)
		}
	})
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "index.html")
	})

	server := http.Server{Addr: "127.0.0.1:0", Handler: mux}
	listener, _ := net.Listen("tcp", server.Addr)
	log.Println("[Addr]:", listener.Addr())

	if runtime.GOOS == "windows" {
		// 帮助您自动打开浏览器。
		go func() {
			time.Sleep(1 * time.Second)
			if err := exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://"+listener.Addr().String()).Start(); err != nil {
				panic(err)
			}
		}()
	}
	_ = server.Serve(listener)
}

index.html

<h2>TEST RPC</h2>
<div></div>
<script type="module">
  async function rpcCall(method, params) {
    return fetch(location.protocol + "//" + location.hostname + (location.port ? ":" + location.port : "") +
      "/myrpc/", {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        method,
        params,
        id: 0
      })
    })
      .then(response => {
        return response.json()
      })
      .then(result => {
        return result
      })
      .catch(error => {
        console.error(error.message)
      })
  }

  
  const aa = await rpcCall("Arith.Multiply", {a: 2, b: 3})
  console.log(aa);
  

  // more test
  [
    ["Arith.Multiply", {a: 2, b: 3}],
    ["Arith.Add", {a: 2, b: 3}],
  ].forEach(async ([method, params]) => {
    const val = await rpcCall(method, params)
    document.querySelector("div").innerHTML += JSON.stringify(val, null, 2) + "<br>"
  })
</script>
英文:

Create a Post and, when a certain condition is met, initiate a TCP connection and feed the data into conn.Call.

for example

http.HandleFunc(&quot;/myrpc/&quot;, func(w http.ResponseWriter, r *http.Request) {
	// check
	{
		// check Method
		if r.Method != http.MethodPost {
			http.Error(w, &quot;Method not allowed&quot;, http.StatusMethodNotAllowed)
			return
		}
		// check Content-Type
		// check Auth
		// ...
	}

	// Get user input
	var req RPCRequest
	{
		_ = json.NewDecoder(r.Body).Decode(&amp;req)
	}

	conn, _ := rpc.Dial(&quot;tcp&quot;, &quot;127.0.0.1:12345&quot;)
	defer conn.Close()

	var reply string
	if err := conn.Call(req.Method, req.Params, &amp;reply); err != nil { // &lt;--
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// handle response
	w.Header().Set(&quot;Content-Type&quot;, &quot;application/json; charset=utf-8&quot;)
	resultBytes, _ := json.Marshal(struct {
		Msg string
	}{
		reply,
	})
	
	_, _ = fmt.Fprintln(w, string(resultBytes))
})

A Runable example

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net&quot;
	&quot;net/http&quot;
	&quot;net/rpc&quot;
	&quot;os/exec&quot;
	&quot;runtime&quot;
	&quot;strings&quot;
	&quot;time&quot;
)

type RPCRequest struct {
	Method string
	Params json.RawMessage
	Id     int
}

type Arith int

func (t *Arith) Multiply(args *json.RawMessage, reply *string) error {
	para := struct {
		A int `json:&quot;a&quot;`
		B int `json:&quot;b&quot;`
	}{}

	if err := json.Unmarshal(*args, &amp;para); err != nil {
		return err
	}
	*reply = fmt.Sprintf(&quot;%d&quot;, para.A*para.B)
	return nil
}

func (t *Arith) unMarshal(args *json.RawMessage) (*struct{ A, B int }, error) {
	output := new(struct{ A, B int })
	err := json.Unmarshal(*args, output)
	return output, err
}

func (t *Arith) Add(args *json.RawMessage, reply *string) error {
	para, err := t.unMarshal(args)
	if err != nil {
		return err
	}
	*reply = fmt.Sprintf(&quot;%d&quot;, para.A+para.B)
	return nil
}

func NewRPCServer() (listener net.Listener, runFunc func()) {
	rpcServer := rpc.NewServer()
	// rpcServer.RegisterName(&quot;Arith&quot;, new(Arith)) // same as below
	if err := rpcServer.Register(new(Arith)); err != nil {
		log.Fatal(err)
	}

	listener, _ = net.Listen(&quot;tcp&quot;, &quot;127.0.0.1:0&quot;)
	log.Println(&quot;[RPC SERVER]&quot;, listener.Addr().String())
	runFunc = func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Println(&quot;[listener.Accept]&quot;, err)
				return
			}

			go func(conn net.Conn) {
				defer func() {
					if err = conn.Close(); err != nil &amp;&amp; !strings.Contains(err.Error(), &quot;use of closed network connection&quot;) {
						log.Println(err)
					}
				}()
				_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
				rpcServer.ServeConn(conn)
			}(conn)
		}
	}
	return listener, runFunc
}

var RPCServerAddr *string

func main() {
	listenerRPC, rpcServerRunFunc := NewRPCServer()
	defer func() {
		_ = listenerRPC.Close()
	}()
	go rpcServerRunFunc()

	RPCServerAddr = new(string)
	*RPCServerAddr = listenerRPC.Addr().String()
	mux := http.NewServeMux()
	mux.HandleFunc(&quot;/myrpc/&quot;, func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, &quot;Method not allowed&quot;, http.StatusMethodNotAllowed)
			return
		}
		if r.Header.Get(&quot;Content-Type&quot;) != &quot;application/json&quot; {
			http.Error(w, &quot;content type error&quot;, http.StatusBadRequest)
			return
		}

		var req RPCRequest
		if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		conn, err := rpc.Dial(&quot;tcp&quot;, *RPCServerAddr)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		defer func() {
			_ = conn.Close()
		}()

		var reply string
		if err = conn.Call(req.Method, req.Params, &amp;reply); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// handle response
		w.Header().Set(&quot;Content-Type&quot;, &quot;application/json; charset=utf-8&quot;)
		resultBytes, _ := json.Marshal(struct {
			Msg   string
		}{
			reply,
		})
		if _, err = fmt.Fprintln(w, string(resultBytes)); err != nil {
			log.Println(err)
		}
	})
	mux.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, &quot;index.html&quot;)
	})

	server := http.Server{Addr: &quot;127.0.0.1:0&quot;, Handler: mux}
	listener, _ := net.Listen(&quot;tcp&quot;, server.Addr)
	log.Println(&quot;[Addr]:&quot;, listener.Addr())

	if runtime.GOOS == &quot;windows&quot; {
		// help you open the browser automatically.
		go func() {
			time.Sleep(1 * time.Second)
			if err := exec.Command(&quot;rundll32&quot;, &quot;url.dll,FileProtocolHandler&quot;, &quot;http://&quot;+listener.Addr().String()).Start(); err != nil {
				panic(err)
			}
		}()
	}
	_ = server.Serve(listener)
}

index.html

&lt;h2&gt;TEST RPC&lt;/h2&gt;
&lt;div&gt;&lt;/div&gt;
&lt;script type=&quot;module&quot;&gt;
  async function rpcCall(method, params) {
    return fetch(location.protocol + &quot;//&quot; + location.hostname + (location.port ? &quot;:&quot; + location.port : &quot;&quot;) +
      &quot;/myrpc/&quot;, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Content-Type&#39;: &#39;application/json&#39;
      },
      body: JSON.stringify({
        method,
        params,
        id: 0
      })
    })
      .then(response =&gt; {
        return response.json()
      })
      .then(result =&gt; {
        return result
      })
      .catch(error =&gt; {
        console.error(error.message)
      })
  }

  
  const aa = await rpcCall(Arith.Multiply&quot;, {a: 2, b: 3})
  console.log(aa);
  

  // more test
  [
    [&quot;Arith.Multiply&quot;, {a: 2, b: 3}],
    [&quot;Arith.Add&quot;, {a: 2, b: 3}],
  ].forEach(async ([method, params]) =&gt; {
    const val = await rpcCall(method, params)
    document.querySelector(&quot;div&quot;).innerHTML += JSON.stringify(val, null, 2) + &quot;&lt;br&gt;&quot;
  })
&lt;/script&gt;

huangapple
  • 本文由 发表于 2022年6月22日 02:17:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/72705456.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定