Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest supporting POJO as request parameter #1253

Closed
codefromthecrypt opened this issue Aug 12, 2016 · 17 comments
Closed

Suggest supporting POJO as request parameter #1253

codefromthecrypt opened this issue Aug 12, 2016 · 17 comments

Comments

@codefromthecrypt
Copy link

From @wanghongfei on August 12, 2016 4:14

I'm using Feign with Spring Cloud Netflix stack. In SpringMVC we can use POJO as request parameter which is very convenient:

@RequestMapping(value = "/group/list")
    public List<AdvertGroupVO> list(AdvertGroupVO vo) throws AlanException {

        return adGroupService.list(vo);
    }

But in Feign I have to write lots of @RequestParam:

@FeignClient(name = "fooService")
public interface foo {

    @RequestMapping(value = "/group/list")
    public List<AdvertGroupVO> list(@RequestParam Integer id,
                                      @RequestParam String name,
                                      @RequestParam ... ...);
}

I think it good for feign's user. Thanks!

Copied from original issue: OpenFeign/feign#438

@codefromthecrypt
Copy link
Author

In feign's DefaultContract, a parameter without any annotations is assumed to be processed by feign's Encoder, which can do what's been requested here. We can decide if this behavior makes sense or not.

@wanghongfei
Copy link

wanghongfei commented Aug 12, 2016

Thanks for @adriancole 's reply. In current Spring Cloud Netflix Feign's implementation it seems feign doesn't encode POJO to HTTP request parameter by default, or there's something I do not know yet

@codefromthecrypt
Copy link
Author

@wanghongfei are you saying that you want something to literally write each field as a query parameter (ex via reflection)? If so, that would be a custom encoder and I don't think it is a high-reuse thing at this point.

@codefromthecrypt
Copy link
Author

closest thing in feign upstream is @QueryMap which explodes a map into query parameters. I'm not sure if that is supported in MVC @RequestParam

@wanghongfei
Copy link

@adriancole Thanks, I'll try @QueryMapor define my own encoder.

@wongloong
Copy link

@wanghongfei 请问这个问题你最后怎么解决的?

@asarkar
Copy link
Contributor

asarkar commented Jun 25, 2017

@adriancole @wanghongfei My PR #1361 allows @RequestParam to be used without a value/name and with a Map. Not with a POJO, but that's the closest thing to POJO.

@barrer
Copy link

barrer commented Aug 18, 2017

@wongloong
supporting POJO as request parameter
solution: http://www.itmuch.com/spring-cloud-sum/feign-multiple-params/

Let's talk about how to use Feign to construct a POST request with multiple parameters. Assume that the Service Provider's Controller is written as follows:
@RestController
public class UserController {
  @PostMapping("/post")
  public User post(@RequestBody User user) {
    ...
  }
}
How do we use Feign to request for it? The answer is very simple, example:
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
  @RequestMapping(value = "/post", method = RequestMethod.POST)
  public User post(@RequestBody User user);
}

tips: method = RequestMethod.POST and @RequestBody
I tested successfully!

@spencergibb
Copy link
Member

Right, this issue is for a GET method, not post.

@SeauWong
Copy link

yml:

feign:
  httpclient:
    enabled: true

MAVEN:

            <!--httpClient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.3</version>
            </dependency>

            <dependency>
                <groupId>com.netflix.feign</groupId>
                <artifactId>feign-httpclient</artifactId>
                <version>8.17.0</version>
            </dependency>

SendPoint(get method):

@RequestMapping(value = "/user/list",method = RequestMethod.GET,consumes = "application/json")
    List<User> users(@RequestBody QueryBean queryBean);

ReceivePoint(post method):

@RequestMapping(value = "/user/list",method = RequestMethod.POST,consumes = "application/json")
    List<User> users(@RequestBody QueryBean queryBean){
                      func();
}

@charlesvhe
Copy link

charlesvhe commented Mar 7, 2018

I use RequestInterceptor to solve this problem:

public class YryzRequestInterceptor implements RequestInterceptor {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void apply(RequestTemplate template) {
        // feign 不支持 GET 方法传 POJO, json body转query
        if (template.method().equals("GET") && template.body() != null) {
            try {
                JsonNode jsonNode = objectMapper.readTree(template.body());
                template.body(null);

                Map<String, Collection<String>> queries = new HashMap<>();
                buildQuery(jsonNode, "", queries);
                template.queries(queries);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) {   // 叶子节点
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) {   // 数组节点
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else {  // 根节点
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }
}

@rmfish
Copy link

rmfish commented Jan 10, 2019

Create a custom SpringMvcContract bean which override processAnnotationsOnParameter method:

https://gist.github.com/rmfish/0ed59a9af6c05157be2a60c9acea2a10

@ryanjbaxter
Copy link
Contributor

I dont think this is still an issue.

@eefnrowe
Copy link

eefnrowe commented Jan 15, 2019

@charlesvhe ^_^

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencyManagement>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Greenwich.RC2</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>

   <!--feign相关-->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
        <version>10.1.0</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-core</artifactId>
        <version>10.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.6</version>
    </dependency>
</dependencyManagement>

@Configurable
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
    @Autowired
    private ObjectMapper objectMapper;

    public FeignBasicAuthRequestInterceptor() {}

    @Override
    public void apply(RequestTemplate template) {
        ///**get-pojo贯穿*/
        if (template.method().equals("GET") && template.requestBody().asBytes() != null) {
            try {
                JsonNode jsonNode = objectMapper.readTree(template.requestBody().asBytes());
                //template.body(null);
                Map<String, Collection<String>> queries = new HashMap<>();
                //feign 不支持 GET 方法传 POJO, json body转query
                buildQuery(jsonNode, "", queries);
                template.queries(queries);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //处理 get-pojo贯穿
    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) { //叶子节点
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) { //数组节点
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else { //根节点
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }
}

server1 - feignRemote:

@PostMapping("/app")
Response<Integer> appInsert(@RequestBody AppDTO dto);

@DeleteMapping("/app/{appId}")
Response<Integer> appDelete(@PathVariable("appId") String appId);

@PutMapping("/app")
Response<Integer> appUpdate(@RequestBody AppDTO dto);

@GetMapping("/app/{appId}")
Response<AppDTO> appGet(@PathVariable("appId") String appId);

@GetMapping("/app")
Response<List<AppDTO>> appList(AppQTO qto);

@GetMapping("/app/listcount")
Response<List<AppDTO>> appListCount(AppQTO qto);

server2 - controller

@PostMapping
public Response<Integer> insert(@RequestBody AppDTO dto) {
    System.out.println("#######insert:"+ JsonUtil.toJson(dto));
    return new Response<>(appService.insert(dto));
}

@DeleteMapping("/{appId}")
public Response<Integer> delete(@PathVariable("appId") String appId) {
    return new Response<>(appService.deleteByIds(appId));
}

@PutMapping
public Response<Integer> update(@RequestBody AppDTO dto) {
    System.out.println("#######update:"+ JsonUtil.toJson(dto));
    return new Response<>(appService.update(dto));
}

@GetMapping("/{appId}")
public Response<AppDTO> get(@PathVariable("appId") String appId) {
    return new Response<>(appService.getObject(appId));
}

@GetMapping
public Response<List<AppDTO>> list(AppQTO qto) {
    System.out.println("#######list:"+ JsonUtil.toJson(qto));
    return new Response<>(appService.listObjects(qto));
}

@GetMapping("/listcount")
public Response<List<AppDTO>> listCount(AppQTO qto) {
    System.out.println("#######listCount:"+ JsonUtil.toJson(qto));
    return appService.list(qto);
}

@LUCKYZHOUSTAR
Copy link

@eefnrowe thanks

@maketubo
Copy link

maketubo commented Feb 19, 2020

@eefnrowe With java1.8 sun.net.www.protocol.http.HttpURLConnection or feign-okhttp 10.4.0, I need to empty requestBody like template.body(Request.Body.empty()); after template.queries(queries); then work fine. 我看到你那边把template.body(null);注释掉了是因为之前版本的httpclient没问题?

@cbjjensen
Copy link

@maketubo I changed it to

        byte[] emptyBody = Request.Body.empty().asBytes();
        if (template.method().equals("GET") && !Arrays.equals(template.body(), emptyBody)) {
        //....
             template.body(Request.Body.empty());
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests