27、集成 Blogger API:使用 GData 协议开发博客编辑器应用

集成 Blogger API:使用 GData 协议开发博客编辑器应用

1. 搭建 HTTP 代理

为了能让应用直接与外部服务交互,我们需要搭建一个 HTTP 代理。将其部署在 /HTTPProxy 路径下,此路径可任意命名,但客户端类在 sendRequest 方法里必须使用相同的值:

RequestBuilder requestBuilder = new RequestBuilder( 
   RequestBuilder.POST, GWT.getModuleBaseURL()+"/HTTPProxy" );

若要把这个 Servlet 部署到像 Tomcat 这样的完整 Java Servlet 容器中,就需要创建一个 web.xml 文件来描述该 Servlet:

<web-app>
   <servlet>
      <servlet-name>HTTPProxy</servlet-name>
      <servlet-class>com.gwtapps.server.util.HTTPProxy</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>HTTPProxy</servlet-name>
      <url-pattern>/HTTPProxy</url-pattern>
   </servlet-mapping>
</web-app>

2. 选择 API 协议

2.1 Blogger API 发展

Blogger 博客服务支持 API 访问已有较长历史,不过其 API 历经多次变更。2003 年 Google 收购了 Blogger 服务背后的 Pyra Labs 公司,2006 年 Google 开始将 Blogger 的 API 迁移至 Google Data API(GData)。

2.2 GData 协议优势

我们将运用 GData 协议来访问 Blogger。尽管在编写本文时旧协议仍可使用,但 Blogger 会逐步淘汰它。GData 协议不仅对与 Blogger 交互有用,Google 还通过该协议提供众多其他服务。而且,GData 基于 Atom 发布协议,此协议对更多 Web 服务也很有用。

2.3 Atom 发布协议与 GData 协议

多数博客服务起初采用基于 RPC 的 API,随着新功能的添加,API 变得愈发复杂,难以管理和学习。于是,一些博客服务和开发者联合创建了 Atom 发布协议,旨在为内容的联合和创作制定通用的发布标准。
- Atom 发布协议 :是一种 REST API,利用 HTTP 方法修改由 URL 标识的数据,并使用 Atom XML 模式描述数据。更多信息可查看 Atom 发布协议
- GData 协议 :基于 Atom 发布协议,提供查询和认证扩展。更多信息可查看 GData 协议

3. 定义 BloggerService 类

我们会在 BloggerService 类中借助 HTTP 代理与 Blogger 服务交互,并更新应用视图。该类将继承 BlogService 抽象基类:

public abstract class BlogService {
   private BlogEditorView view;
   public BlogService( BlogEditorView view ){
      this.view = view;
   }
   public BlogEditorView getView(){
      return view;
   }
   public abstract void signin();
}

public class BloggerService extends BlogService implements BlogViewListener{
   public BloggerService( BlogEditorView view ){
      super(view);
   }
}

BlogService 抽象基类为添加到应用中的任何博客服务提供统一的基类,能让代码同时与多个服务交互,例如简单的登录任务。 BloggerService 类继承该接口,实现 signin 方法以及博客服务的其他所有方法。

4. 登录 Google 账户

使用 GData 协议的应用,登录是较为复杂的任务之一,但与其他认证机制相比,它相对简单。对于像博客编辑器这样的 Web 客户端应用,我们使用 AuthSub 认证接口。

4.1 认证流程

认证流程分为三个步骤:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(获取一次性令牌):::process
    B --> C(交换会话令牌):::process
    C --> D(使用会话令牌请求服务):::process
    D --> E([结束]):::startend
  1. 获取一次性令牌 :将应用重定向到 AuthSub URL: https://www.google.com/accounts/AuthSubRequest ,此 URL 需包含以下参数:
    • next :告知 AuthSub 在用户授权后应将用户重定向到的 URL。
    • scope :指定应用感兴趣的 Google 服务,对于 Blogger,该值为 http://www.blogger.com/feeds
    • session :确定返回的令牌是否可用于获取会话令牌,值为 1 或 0,本应用使用 1。
      组合后的 URL 如下:
      java private static final String URL_AUTH_SUB = "http://www.google.com/accounts/ AuthSubRequest?next="+GWT.getModuleBaseURL()+"index.html&scope=http%3A%2F%2Fwww. blogger.com/feeds&session=1";
  2. 交换会话令牌 :用户授权并返回一次性令牌后,应用使用该令牌向 https://www.google.com/accounts/AuthSubSessionToken 发送请求,将令牌作为 HTTP 请求头中的 Authorization 字段发送:
    Authorization: AuthSub token=”token_value”
    AuthSub 接口返回包含会话令牌的 HTTP 响应,例如:
    Token=DQAA...7DCTN Expiration=20061004T123456Z
  3. 使用会话令牌请求服务 :在每个请求的 HTTP 头中添加会话令牌:
    Authorization: AuthSub token=”session_token_value”

4.2 代码实现

public void signin(){
   //check for a saved token
   if( sessionToken == null ){
      sessionToken = Cookies.getCookie("BloggerAuthSub");
   }
   //if we have a token get the list of blogs
   if( sessionToken != null ){
      makeRemoteCall( URL_GET_BLOG_LIST, new GetBlogListCallback() );
   }
   else{
      //check for a token in the URL parameters
      String params = BrowserLocation.getURLParameters();
      if( params.length() != 0 ){
         String tokenKey = "token=";
         int tokenIndex = params.indexOf(tokenKey);
         //request a session token using single-use token
         if( tokenIndex != -1  ){
            String token = params.substring( tokenIndex+tokenKey.length());
            makeRemoteCall( 
               URL_AUTH_SUB_SESSION, token, new GetTokenCallback() );
            return;
         }
      }
      //we don’t have any tokens so we need to redirect to Google
      BrowserLocation.setLocation( URL_AUTH_SUB );
   }
}

signin 方法首先检查会话令牌是否保存在 cookie 中。若有令牌,继续请求博客列表;若没有,检查 URL 参数中是否有一次性令牌,若有则使用该令牌请求会话令牌;若都没有,则将用户重定向到 AuthSub 接口。

5. 获取账户的博客 XML 列表

用户登录并获得会话令牌后,就可以与 Blogger 服务交互并管理博客。首先要获取用户可管理的博客列表,通过向 http://www.blogger.com/feeds/default/blogs 发送 HTTP GET 请求来实现:

makeRemoteCall( URL_GET_BLOG_LIST, new GetBlogListCallback() );

GetBlogListCallback 类处理服务器响应:

private class GetBlogListCallback implements RequestCallback {
   public void onError(Request request, Throwable exception){ 
      GWT.log( "error", exception ); 
   }
   public void onResponseReceived( Request request, Response response ){
      getView().getLoadingPanel().loadingEnd();
      if( handleResponse( response ) ){
         //parse the response as XML
         Element document = XMLParser.parse( 
            response.getText() ).getDocumentElement();
         //iterate over each entry in the XML
         NodeList items = document.getElementsByTagName("entry");
         for(int i=0;i<items.getLength();i++ ){
            //extract the required data
            Element item = (Element)items.item(i);
            String title = getElementText( item, "title" );
            String link="";
            String postLink="";
            String feedLink="";
            //get the different kinds of links
            NodeList links = item.getElementsByTagName("link");
            for(int j=0;j<links.getLength();j++ ){
               Element linkElement = (Element)links.item(j);
               String rel = linkElement.getAttribute( "rel" );
               if( rel.equals("alternate") )
                  link = linkElement.getAttribute("href");
               else if( rel.equals("http://schemas.google.com/g/2005#post") )
                  postLink = linkElement.getAttribute("href");
               else if( rel.equals("http://schemas.google.com/g/2005#feed") )
                  feedLink = linkElement.getAttribute("href");
            }
            //create a new Blog instance from the XML data
            Blog blog = new Blog( title, link );
            blogPostLinks.put( blog, postLink );
            getView().addBlog( blog, BloggerService.this );
            //request the blog entries for this blog
            makeRemoteCall( feedLink, new GetBlogEntriesCallback( blog ));
         }
      }
   } 
}

该类解析服务器返回的 XML 数据,创建 Blog 实例,并保存博客的发布链接和订阅链接。同时,立即使用订阅链接请求博客的文章列表。

6. 获取每篇博客的文章 XML 列表

加载 Blogger 服务信息的最后一步是获取每篇博客的文章。在解析博客的 XML 描述并获取其订阅 URL 后,立即发起请求:

makeRemoteCall( feedLink, new GetBlogEntriesCallback( blog ) );

GetBlogEntriesCallback 类处理服务器响应:

private class GetBlogEntriesCallback implements RequestCallback{
   private Blog blog;
   GetBlogEntriesCallback( Blog blog ){ 
      this.blog = blog; 
   }
   public void onError(Request request, Throwable exception){ 
      GWT.log( "error", exception ); 
   }
   public void onResponseReceived(Request request, Response response){
      getView().getLoadingPanel().loadingEnd();
      if( handleResponse( response ) ){
         //parse the XML response
         Element document = XMLParser.parse( 
            response.getText() ).getDocumentElement();
         //iterate over each entry in the response
         NodeList items = document.getElementsByTagName("entry");
         for(int i=0;i<items.getLength();i++ ){
            //create a new blog entry and add it to the view
            BlogEntry entry = new BlogEntry( blog );
            blog.addEntry( entry );
            entryFromXml( entry, (Element)items.item(i));
            getView().getBlogView( blog ).addEntryAtEnd( entry );
         }
      }
   }
}

该类解析服务器返回的 XML 数据,为每个文章元素创建 BlogEntry 实例,并将其添加到视图中。 entryFromXml 方法用于将 XML 数据复制到 BlogEntry 实例:

private void entryFromXml( BlogEntry entry, Element entryElement ){
   entry.setTitle( getElementText( entryElement, "title" ) );
   entry.setContent( getElementText( entryElement, "content" ) );
   String editLink = "";
   NodeList links = entryElement.getElementsByTagName("link");
   for(int j=0;j<links.getLength();j++ ){
      Element linkElement = (Element)links.item(j);
      String rel = linkElement.getAttribute( "rel" );
      if( rel.equals("alternate") )
         entry.setLink( linkElement.getAttribute("href") );
      else if( rel.equals("edit") )
         editLink = linkElement.getAttribute("href");
   }
   entryEditLinks.put( entry, editLink );
}

private static String getElementText( Element item, String value ){
   String result = "";
   NodeList itemList = item.getElementsByTagName(value);
   if( itemList.getLength() > 0 && itemList.item(0).hasChildNodes()){
      result = itemList.item(0).getFirstChild().getNodeValue();
   }
   return result;
}

7. 发送 XML 以创建和保存文章

视图允许用户在博客上创建新文章并修改现有文章。当用户保存文章时,视图会调用 BlogViewListener 接口的 onEntrySaved 方法, BloggerService 类实现了该接口并负责在服务器上执行相应操作。

7.1 操作步骤

操作步骤如下:
1. 确定文章是新文章还是现有文章:通过在哈希表中搜索文章的编辑 URL 来判断。
2. 选择合适的 URL:如果是新文章,使用博客的发布链接;如果是现有文章,使用编辑链接。
3. 发送 HTTP 请求:使用 HTTPProxyRequestBuilder 类发送请求,并设置请求头和请求体。

7.2 代码实现

public void onEntrySaved( BlogEntry entry ){
   getView().getSavingPanel().loadingBegin();
   //create the request object 
   HTTPProxyRequestBuilder builder;
   String editLink = (String)entryEditLinks.get( entry );
   if( editLink == null ){
      String postLink = (String)blogPostLinks.get( entry.getBlog() );
      builder = new HTTPProxyRequestBuilder( RequestBuilder.POST, postLink );
   }
   else{
      builder = new HTTPProxyRequestBuilder(RequestBuilder.POST, editLink );
      builder.setHeader("X-HTTP-Method-Override", "PUT");
   }
   //set authorization to our token and set the content type to XML
   builder.setHeader("Authorization", "AuthSub token=\""+sessionToken+"\"");
   builder.setHeader("Content-type", "application/atom+xml");
   //send the request using the entry XML as the body
   try {
      builder.sendRequest( 
         entryToXml( entry ), new PostEntryCallback( entry ) );
   } 
   catch (RequestException e){ GWT.log( "error", e); }
}

7.3 注意事项

  • 由于 Safari 浏览器的限制,我们使用 X-HTTP-Method-Override 头来强制使用 PUT 方法。
  • entryToXml 方法用于将 BlogEntry 实例转换为 XML 字符串:
private String entryToXml( BlogEntry entry ){
   Document document = XMLParser.createDocument();
   Element entryElement = document.createElement("entry");
   Element titleElement = document.createElement("title");
   Element contentElement = document.createElement("content");
   Text titleText = document.createTextNode( entry.getTitle());
   Text contentText = document.createTextNode( entry.getContent() );
   document.appendChild(entryElement);
   entryElement.setAttribute("xmlns","http://www.w3.org/2005/Atom");
   entryElement.appendChild(titleElement);
   entryElement.appendChild(contentElement);
   titleElement.appendChild(titleText);
   contentElement.appendChild(contentText);
   contentElement.setAttribute("type","xhtml");
   return document.toString();
}

7.4 处理响应

PostEntryCallback 类处理服务器的响应:

private class PostEntryCallback implements RequestCallback{
   BlogEntry entry;
   PostEntryCallback( BlogEntry entry ){ 
      this.entry = entry; 
   }
   public void onError(Request request, Throwable exception){ 
      GWT.log( "error", exception ); 
   }
   public void onResponseReceived( Request request, Response response ){
      getView().getSavingPanel().loadingEnd();
      if( handleResponse( response ) ){
         entryFromXml( entry, response.getText() );
         getView().getBlogView( entry.getBlog() )
            .getEntryView( entry ).update();
      }
   }
}

该类将服务器返回的更新后的文章数据复制回 BlogEntry 实例,并通知视图更新显示。

8. 发送删除文章的请求

我们还需要实现 REST 方法中的 DELETE 方法,用于删除文章。 BloggerService 类实现了 BlogViewListener 接口的 onEntryDeleted 方法:

8.1 操作步骤

操作步骤如下:
1. 获取文章的编辑链接:从哈希表中获取文章的编辑链接。
2. 创建 HTTP 请求:使用 HTTPProxyRequestBuilder 类创建请求,并设置请求头。
3. 发送请求:使用 sendRequest 方法发送请求,并处理响应。

8.2 代码实现

public void onEntryDeleted( BlogEntry entry ){
   //create the request
   HTTPProxyRequestBuilder builder;
   String editLink = (String)entryEditLinks.get( entry );
   if( editLink != null ){
      builder = new HTTPProxyRequestBuilder( RequestBuilder.POST, editLink );
      //set the authorization, content type, and method override headers
      builder.setHeader("X-HTTP-Method-Override", "DELETE");
      builder.setHeader(
         "Authorization", "AuthSub token=\""+sessionToken+"\"");
      builder.setHeader("Content-type", "application/atom+xml");
      //send the request
      try {
         builder.sendRequest(entryToXml( entry ), new DeleteEntryCallback());
      } 
      catch (RequestException e){ GWT.log( "error", e); }
   }
}

8.3 处理响应

DeleteEntryCallback 类处理服务器的响应:

private class DeleteEntryCallback implements RequestCallback {
   public void onError(Request request, Throwable exception){ 
      GWT.log( "error", exception ); 
   }
   public void onResponseReceived(Request request, Response response){
      handleResponse( response );
   }
}

该类简单地处理响应,确保删除操作成功。需要注意的是,这里没有向用户验证文章即将被删除,因为视图层已经处理了用户确认的逻辑。

总结

通过以上步骤,我们实现了一个完整的博客编辑器应用,该应用可以与 Blogger 服务进行交互,包括登录、获取博客列表、获取文章列表、创建和保存文章以及删除文章等功能。以下是整个应用的操作流程总结:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(登录 Google 账户):::process
    B --> C(获取博客列表):::process
    C --> D(获取文章列表):::process
    D --> E{操作选择}:::decision
    E -->|创建文章| F(发送 XML 创建文章):::process
    E -->|保存文章| G(发送 XML 保存文章):::process
    E -->|删除文章| H(发送删除请求):::process
    F --> I([结束]):::startend
    G --> I
    H --> I

整个应用的核心在于使用 GData 协议与 Blogger 服务进行交互,通过 HTTP 代理解决跨域问题,并使用 AuthSub 接口进行认证。在开发过程中,我们需要注意不同浏览器的兼容性问题,例如 Safari 浏览器对 PUT DELETE 方法的支持。同时,我们还需要处理服务器返回的响应,确保数据的一致性和准确性。通过合理的设计和实现,我们可以开发出一个功能强大、用户友好的博客编辑器应用。

内容概要:本文介绍了一个基于Matlab的综合能源系统优化调度仿真资源,重点实现了含光热电站、有机朗肯循环(ORC)和电含光热电站、有机有机朗肯循环、P2G的综合能源优化调度(Matlab代码实现)转气(P2G)技术的冷、热、电多能互补系统的优化调度模型。该模型充分考虑多种能源形式的协同转换与利用,通过Matlab代码构建系统架构、设定约束条件并求解优化目标,旨在提升综合能源系统的运行效率与经济性,同时兼顾灵活性供需不确定性下的储能优化配置问题。文中还提到了相关仿真技术支持,如YALMIP工具包的应用,适用于复杂能源系统的建模与求解。; 适合人群:具备一定Matlab编程基础和能源系统背景知识的科研人员、研究生及工程技术人员,尤其适合从事综合能源系统、可再生能源利用、电力系统优化等方向的研究者。; 使用场景及目标:①研究含光热、ORC和P2G的多能系统协调调度机制;②开展考虑不确定性的储能优化配置与经济调度仿真;③学习Matlab在能源系统优化中的建模与求解方法,复现高水平论文(如EI期刊)中的算法案例。; 阅读建议:建议读者结合文档提供的网盘资源,下载完整代码和案例文件,按照目录顺序逐步学习,重点关注模型构建逻辑、约束设置与求解器调用方式,并通过修改参数进行仿真实验,加深对综合能源系统优化调度的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值