我所理解的Restful

追根溯源

理解一个概念最好的一个方法就是知其来龙去脉,最简单暴力的方式就是直接上结论。

知识并不仅仅是其表面上看起来那么枯燥乏味的,也可以很性感。就像《七堂极简物理课》的作者,虽然是个物理学家,讲的也是物理学世界里博大精深的理论,但是他以诗的语言,讲诉了物理学家发现每个理论碰到的困难和惊喜,有血有肉,让人沉浸其中。

今天,我们的主角是个IT界牛人中的牛人,意思就是BAT这样的牛人,听到这个名字,也会说,他是个牛人,自己只是个小学生。

REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。Fielding何许人也,他是HTTP协议(1.0版和1.1版)的主要设计者Apache服务器软件的作者之一Apache基金会的第一任主席。这是个大牛人。

Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。翻译是"表现层状态转化", 这个概念很重要,这里划重点喽。那么,如果一个架构符合REST原则,就称它为RESTful架构

REST——表现层状态转化

REST的名称"表现层状态转化"中,省略了主语,主语就是资源(Resources)。 何谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实物。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。

表现层(Representation),"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。 比如,文本内容可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

划重点啦,状态转化(State Transfer)。互联网通信协议HTTP协议,是一个无状态协议,这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源

综合上面的解释,我们总结一下什么是RESTful架构:
(1)资源用URI表示;
(2)客户端和服务器之间,在表现层进行资源的状态转换;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

接口设计误区

最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。

举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。

GET /POSTS/1

技术实现细节——网络框架

图片
  1. bean文件夹放置DTO,以后跟服务器端协定好DTO,将所有放置此处,可修改
  2. requests文件夹放置网络操作,推荐按模块区分,一个模块一个类,模块下的网络操作直接写成一个方法,可修改
  3. HttpBaseDownload实现下载方法,HttpBaseUpload实现上传方法,但我们现在不case;
  4. 接下来我们重点关注HttpBaseReq这个类,这个类封装了RestFul所需要的四个表示操作方式。

技术实现细节——核心类HttpBaseReq

看看HttpBaseReq类方法


现在直接帮你划重点啦,方法取名已经很明确了,直接按操作动作选方法。几个参数的意思在使用处再详细讲解。

挑取requestPut方法瞅两眼

public static <E> int requestGetWithDialog(Context activity, String url, E mBean, final NetProcessCallback processCallback,
                                               final NetResponseCallback callback) {
        //1. 网络请求实现
        int taskId = requestImp(url, HttpRequest.RequestMethod.GET, mBean, processCallback, callback);
        //2. 起小海快跑弹窗
        if (taskId > 0) {
            showDialog(activity, taskId);
        }
        //3. 返回网络请求唯一标示
        return taskId;
    }

这个方法与requestPostWithDialog、requestDeleteWithDialog等不同操作代码相同,不同动作的区分靠HttpRequest.RequestMethod这个枚举,Get请求传递HttpRequest.RequestMethod.GET,post请求传递HttpRequest.RequestMethod.POST。

哦,宝藏藏在requestImp,让我们继续深挖下去

private static <E> int requestImp(String url, final HttpRequest.RequestMethod requestMethod, E mBean, final NetProcessCallback
            processCallback, final NetResponseCallback callback) {
        ... ...
        //1. 将入参Bean实体转换未Json对象jsonObject
        // class -> String
        if (mGson == null) {
            mGson = new Gson();
        }
        JsonObject jsonObject = null;
        String k = null;
        if (mBean != null) {
            k = mGson.toJson(mBean);
            // string  -> JsonObject
            if (sJsonParser == null) {
                sJsonParser = new JsonParser();
            }
            jsonObject = sJsonParser.parse(k).getAsJsonObject();
            k = jsonObject.toString();
        }
        //2. get和delete动作 将url和k组合,组合成http://baidu.com?name=jack形式
        if ((requestMethod == HttpRequest.RequestMethod.GET || requestMethod == HttpRequest.RequestMethod.DELETE)
                && jsonObject != null && jsonObject.size() > 0) {
            k = null;
            // 将url和k组合
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(url);
            stringBuilder.append("?");
            for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                stringBuilder.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("&");
            }
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
            url = stringBuilder.toString();
            url = url.replaceAll("\"", "");//临时替换双引号
        }

        int taskId = 0;
        //3. 调用田少的RESTful网络底层
        taskId = HttpRequest.getInstance().requestWithRESTful(url, requestMethod, getHeadJson(), k, new NetworkCallback() {
            ... ...
        });
        if (taskId <= 0) {
            callback.netFailure("请求下发失败");
        }
        return taskId;
    }

代码逻辑有点多了,帮你做个小结吧

  1. 将入参Bean实体转换未Json对象jsonObject;
  2. Delete和Get请求,需要将路径url和入参k组合,组合成http://baidu.com?value=jack 形式;
  3. Post和Patch请求,将入参作为postData数据传输。

此时我的眼睛瞄准了requestWithRESTful

public int requestWithRESTful(String url, HttpRequest.RequestMethod method, String jsonHeadLines, String bodyData, NetworkCallback networkCallbacks) {
        Log.v(Log.XH_NETWORK_CLIENT_TAG, " url=" + url);
        Log.v(Log.XH_NETWORK_CLIENT_TAG, " method=" + method);
        Log.v(Log.XH_NETWORK_CLIENT_TAG, " jsonHeadLines=" + jsonHeadLines);
        Log.v(Log.XH_NETWORK_CLIENT_TAG, " bodyData=" + bodyData);
        JSONObject jsonParam = new JSONObject();
        try {
            jsonParam.put("requestMethod", method.toString());
            if(bodyData != null && !method.equals(HttpRequest.RequestMethod.DELETE) && !method.equals(HttpRequest.RequestMethod.GET)) {
                jsonParam.put("postData", bodyData);
            }

            if(jsonHeadLines != null) {
                jsonParam.put("headList", jsonHeadLines);
            } else {
                JSONObject e = new JSONObject();
                e.put("contentType", "application/json");
                jsonParam.put("headList", e.toString());
            }
        } catch (JSONException var8) {
            var8.printStackTrace();
        }

        return this.request(url, jsonParam.toString(), networkCallbacks);
    }
  1. 田少requestMethod里存放请求动作;
  2. headList放置自行协议的请求头;
  3. postData放置非DELETE请求和非GET请求的请求数据。

协议的请求头

往回掰代码,找到getHeadJson方法

/**
     * @return 返回Header头
     */
    private static String getHeadJson() {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("Content-Type", "application/json; charset=UTF-8");
        String packageName = XHApplication.sPackageName;
        String versionName = XHApplication.sVersionName;
        String releaseName = Build.VERSION.RELEASE;
        String model = android.os.Build.MODEL;
        String suuid = XHApplication.sUUID;
        String userAgent = String.format(Locale.getDefault(), "%s/%s (%s; android; %s; %s)",
                packageName, versionName, model, releaseName, suuid);
        jsonObject.addProperty("User-Agent", userAgent);
        String session = AppConfig.get(AppConfig.KEY_SESSION);
        if(!TextUtils.isEmpty(session)){
            String auth = String.format(Locale.getDefault(), "Bearer %s", session);
            jsonObject.addProperty("Authorization", auth);
        }

        return jsonObject.toString();
    }
  1. Content-Type: 内容类型,可以有application/xml,text/xml,我们现在统一application/json;
  2. User-Agent传输设备、客户端相关的信息:

格式 User-Agent: 应用名称或包名/应用版本号 (设备型号; android|ios; 系统版本号; 5.0设备串号|U) > U表示未定义或未知
比如:User-Agent: com.xh.app.yuedu/1.0 (P350; android; 5.0; 设备串号)

  1. Authorization头传输用户认证的token:

格式 Authorization: Bearer sessionId;
如 Authorization: Bearer 4086c8d4-8ca4-42d3-a900-c9e7a5e8329d;

如何使用

一个Get请求

服务器端接口定义:



网络请求:

/**
     * des:获取文章评论列表
     * author:wqk
     * date:2017/3/9 0009 14:46
     * String exampel="http://192.168.5.29:8082/v1/articles/5/comments?userId=32673&page=0&size=10";
     */
    public static void loadArticleCommentList(Context context, RequestCommentListBean bean, NetResponseCallback callback) {
        String token = XHApplication.getXHApplication().getM();
        bean.setUserId(token);
         //0. 拼装路径
        String url = String.format(Locale.getDefault(), "%sarticles/%s/comments", NetConst.ROOT_URL, bean.getArticleId());
        HttpBaseReq.requestGetWithDialog(context, url, bean, new NetProcessCallback() {
            @Override
            public void dataProcessor(Object object) {
                 //1. String类型的数据用Gson转换成实体类数据
                ArticleCommentListResponse response = (new Gson()).fromJson(
                        object.toString(), ArticleCommentListResponse.class);
                //2. 数据存储,可存配置,存数据库,存文件
                AppConfig.put(NetConst.LOAD_ARTICLE_COMMENT_LIST_RESPONSE, response);
            }
        }, callback);
    }
  1. url的拼接需要用户usId和文章id,表示某人(UsId)获取(Get)某篇文章(ArticleId)下的评论;
  2. NetProcessCallback用作解析数据,方法是子线程调用,不阻塞主线程,里面可执行解析数据,存储数据操作;
  3. bean作为参数传入requestGetWithDialog方法,bean的数据会以key=value的方式拼接到路径的?后面。

一个Post请求

服务器接口定义:



网络请求:

/**
     * 保存评论,无数据返回,可不解析数据
     */
    public static void saveComment(Context context, SaveCommentBean bean, NetResponseCallback callback) {
        String usId = XHApplication.getXHApplication().getM();
        bean.setUserId(NumberUtil.toInt(usId));
        String url = String.format(Locale.getDefault(), "%sarticles/%s/comments", NetConst.ROOT_URL, bean.getArticleId());
        HttpBaseReq.requestPostWithDialog(context, url, bean, null, callback);
    }
  1. 无数据返回,可不解析数据,NetProcessCallback参数可传null;

一个Delete请求

服务器接口定义:

/**
     * 删除评论, ?后无数据,bean入参可传null, 无数据返回,可不解析数据
     */
public static void deleteComment(Context context, DeleteCommentBean bean, NetResponseCallback callback) {

        String url = String.format(Locale.getDefault(), "%sarticles/%s/comments/%s"
                , NetConst.ROOT_URL, bean.getArticleId(), bean.getCommentId());
        HttpBaseReq.requestDeleteWithDialog(context, url, null, null, callback);
    }

鸣谢

理解RESTful架构

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 《转》,非原创 越来越多的人开始意识到,网站即软件,而且是一种新型的软件。 这种"互联网软件"采用客户端/服务器模...
    原军锋阅读 294评论 0 0
  • 作者: 阮一峰日期: [2011年9月12日] 越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种"互...
    liwei_happyman阅读 116评论 0 0
  • 越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种"互联网软件"采用客户端/服务器模式,建立在分布式体...
    你清澈又神秘阅读 175评论 0 1
  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,423评论 0 19