<p>I'm trying to share a structure between http.Handlers with the possibility to change its value depending by the r.Path. This is what I've done so far, but it gives me <strong>cannot assign error</strong> </p>
<p><a href="https://play.golang.org/p/NKPlslIWyI" rel="nofollow">https://play.golang.org/p/NKPlslIWyI</a></p>
<p>I'm looking to find out if there's any workaround or if I should switch to a framework that provides an context..Thanks!</p>
<hr/>**评论:**<br/><br/>lansellot: <pre><p>You need to use this:</p>
<pre><code>u.(*User).id
</code></pre>
<p>Because "u" in your context is of type *User, not User. Remember, you're storing a &User{}, not a User{}.</p>
<p><a href="https://play.golang.org/p/VMjSUyX8Ug" rel="nofollow">https://play.golang.org/p/VMjSUyX8Ug</a></p>
<p>Then the code runs, but hits a deadlock.</p></pre>shark1337: <pre><p>Not really, by using *User it won't match the interface assertion. Either way, it's not working.</p></pre>lansellot: <pre><p>Using *User compiles. There is no interface in your code.</p></pre>shark1337: <pre><p>http: panic serving 127.0.0.1:50570: interface conversion: interface {} is main.User, not *main.User </p>
<p>I'm not trying to be mean or whatever you might be thinking, but the code he provides throws this error that doesn't give me a straight answer..I'm starting to think that the way context.Context was build to work with http, will make changes locally on the specified path and using pointers won't solve it..</p></pre>lansellot: <pre><p>If the errors on play.golang.org and your machine are different, there isn't much we can do.</p></pre>shark1337: <pre><p>Linux all the way up, shouldn't be a problem. The most probably trying to make a http server on go playground might not bet allowed.</p></pre>epiris: <pre><p>That is because you didn't change ALL your code, just one line. Your play example does this:</p>
<pre><code>u := &User{"#$@!$!dsqd2"}
ctx := context.WithValue(r.Context(), "u", *u)
</code></pre>
<p>But this doesn't make sense. You create a variable using the address operation of type *User, then in the very next line you perform pointer indirection to store a type of User in the context value.</p>
<p>Then to retrieve it later you tried to do:</p>
<pre><code>u := r.Context().Value("u")
u.(*User).id
</code></pre>
<p>This is wrong as the reply said, it's easier to see if you put it together without the context.</p>
<pre><code>u := &User{"#$@!$!dsqd2"} // var u *User = &User{...}
var eface interface{} = *u // underlying type User (not a pointer)
eface.(*User).id // This is impossible, eface is a User not *User
</code></pre>
<p>So you need to store the type you want to access. Either store *User and access *User or store User and access User. My guess is you want a pointer because you complain "the interface doesn't match" later so you have a interface User implements that requires it's method receiver to be a pointer type.</p>
<p>When you post problems you should post the complete code needed to reproduce it and make sure to be receptive to help if your goal is to get back to programming! :D</p></pre>shark1337: <pre><p>The only reason I wanna use a pointer is to modify the context value for specific Request Path. For now, I figured it out that every path has it's local value, couldn't find a way to modify the value from path to path so I decided to use a pointer but didnt work as expected. Anyway, pretty much that's not how context was build to work..</p></pre>epiris: <pre><p>You were not using a pointer... that is the entire point of my post. The error tells me this clearly as well as your code on play confirms it:</p>
<pre><code>interface conversion: interface {} is main.User, not *main.User
</code></pre>
<p>Because your code is:</p>
<pre><code>ctx := context.WithValue(r.Context(), "u", *u)
</code></pre>
<p>But it should be:</p>
<pre><code>ctx := context.WithValue(r.Context(), "u", u) // no ptr indirection
</code></pre>
<p>Again you ignore the correct answers you asked for and create your own incorrrect discovery "I figured it out that every path has its own local value,". This doesn't make sense on its own but given what you've said this far I can infer this means you are still.... passing user by value, making changes to it in each handler and then expecting convergence to the top level context. Its been explained to you so I won't reiterate, good luck I guess.</p></pre>dobegor: <pre><p>The thing is OP assumes that context persists between different HTTP requests.</p></pre>shark1337: <pre><p>Running your code locally will give a wrong interface match..</p></pre>lansellot: <pre><p>That simply means that your local code is different from what you put on play.golang.org.</p></pre>epiris: <pre><p>Yes, really. Why ask for help if you discard the answers lol? This has nothing to do with context, it's basic Go. How to store a T in a interface and retrieve it again using a type assertion. </p></pre>cube2222: <pre><p>First of all, in regards to coding style, you really <em>should</em> user a pointer to a user as the other commentor said.</p>
<p>However, to fix your code, it should be like that:</p>
<p><a href="https://play.golang.org/p/mtIk9AJ1yM" rel="nofollow">https://play.golang.org/p/mtIk9AJ1yM</a></p>
<p>You have to save the variable after casting so you can modify it.</p>
<p>To make what the other commentor said work, you also have to do:
<code>
ctx := context.WithValue(r.Context(), "u", u) // no dereference here
</code></p></pre>shark1337: <pre><p>Yep, that's working as expected, but when I come back to "/" path, the value remains unchanged..</p></pre>cube2222: <pre><p>If you use a pointer, then you should be working on the one and only copy. If you're not using a pointer, then no wonder -> you are owrking on a copy of the variable.</p></pre>dobegor: <pre><p>OP assumes context persists on different requests, which isn't true.</p></pre>hipone: <pre><p>Just a side-note, you can simplify the following part:</p>
<pre><code>if u := r.Context().Value("u"); u != nil {
...
u.(*User).id = "Changed"
</code></pre>
<p>To:</p>
<pre><code>if u, ok := r.Context().Value("u").(*User); ok {
...
u.id = "Changed"
</code></pre>
<p>The same applies to map[string]interface{}, I seen the following code more than I wished to:</p>
<pre><code>if v, ok := m[key]; ok {
if s, ok := v.(string); ok {
...
</code></pre>
<p>It can be simplified to:</p>
<pre><code>if s, ok := m[key].(string); ok {
...
</code></pre></pre>dobegor: <pre><p>I guess you think that context will persist across different requests.
It won't. You should use cookies / local storage for that.</p>
<p>Context is request-scoped, <em>each</em> request will spawn a new, empty context.</p></pre>schumacherfm: <pre><p>You should avoid using context.Context to pass objects around.
Why: <a href="https://faiface.github.io/post/context-should-go-away-go2/" rel="nofollow">https://faiface.github.io/post/context-should-go-away-go2/</a>
Solution: <a href="https://blog.merovius.de/2017/06/18/how-not-to-use-an-http-router.html" rel="nofollow">https://blog.merovius.de/2017/06/18/how-not-to-use-an-http-router.html</a></p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传